重温基础:ES6系列(五)

640?wx_fmt=png

ES6系列目录

  • 1 let 和 const命令

  • 2 变量的解构赋值

  • 3 字符串的拓展

  • 4 正则的拓展

  • 5 数值的拓展

  • 6 函数的拓展

  • 7 数组的拓展

  • 8 对象的拓展

  • 9 Symbol

  • 10 Set和Map数据结构

  • 11 Proxy

  • 12 Promise对象

  • 13 Iterator和 for...of循环

  • 14 Generator函数和应用

  • 15 Class语法和继承

  • 16 Module语法和加载实现

所有整理的文章都收录到我《Cute-JavaScript》系列文章中,访问地址:http://js.pingan8787.com

12 Promise对象

12.1 概念

主要用途:解决异步编程带来的回调地狱问题Promise简单理解一个容器,存放着某个未来才会结束的事件(通常是一个异步操作)的结果。通过 Promise对象来获取异步操作消息,处理各种异步操作。

Promise对象2特点

  • 对象的状态不受外界影响

Promise对象代表一个异步操作,有三种状态:pending(进行中)fulfilled(已成功)rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果

Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意,为了行文方便,本章后面的 resolved统一只指 fulfilled状态,不包含 rejected状态。

Promise缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。

  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

12.2 基本使用

Promise为一个构造函数,需要用 new来实例化。

let p = new Promise(function (resolve, reject){	
   if(/*异步操作成功*/){	
       resolve(value);	
   } else {	
       reject(error);	
   }	
})

Promise接收一个函数作为参数,该函数两个参数 resolvereject,有JS引擎提供。

  • resolve作用是将 Promise的状态从pending变成resolved,在异步操作成功时调用,返回异步操作的结果,作为参数传递出去。

  • reject作用是将 Promise的状态从pending变成rejected,在异步操作失败时报错,作为参数传递出去。

Promise实例生成以后,可以用 then方法分别指定 resolved状态和 rejected状态的回调函数。

p.then(function(val){	
    // success...	
},function(err){	
    // error...	
})

几个例子来理解

  • 当一段时间过后, Promise状态便成为 resolved触发 then方法绑定的回调函数。

function timeout (s){	
    return new Promise((resolve, reject){	
        setTimeout(result,ms, 'done');	
    })	
}	
timeout(100).then(val => {	
    console.log(val);	
})
  • Promise新建后立刻执行。

let p = new Promise(function(resolve, reject){	
    console.log(1);	
    resolve();	
})	
p.then(()=>{	
    console.log(2);	
})	
console.log(3);	
// 1	
// 3	
// 2

异步加载图片

function f(url){	
    return new Promise(function(resolve, reject){	
        const img = new Image ();	
        img.onload = function(){	
            resolve(img);	
        }	
        img.onerror = function(){	
            reject(new Error(	
                'Could not load image at ' + url	
            ));	
        }	
        img.src = url;	
    })	
}

resolve函数和 reject函数的参数为 resolve函数或 reject函数p1的状态决定了 p2的状态,所以 p2要等待 p1的结果再执行回调函数。

const p1 = new Promise(function (resolve, reject) {	
  setTimeout(() => reject(new Error('fail')), 3000)	
})	
const p2 = new Promise(function (resolve, reject) {	
  setTimeout(() => resolve(p1), 1000)	
})	
p2	
  .then(result => console.log(result))	
  .catch(error => console.log(error))	
// Error: fail

调用 resolvereject不会结束 Promise参数函数的执行,除了 return:

new Promise((resolve, reject){	
    resolve(1);	
    console.log(2);	
}).then(r => {	
    console.log(3);	
})	
// 2	
// 1	
new Promise((resolve, reject){	
    return resolve(1);	
    console.log(2);	
})	
// 1

12.3 Promise.prototype.then()

作用是为 Promise添加状态改变时的回调函数, then方法的第一个参数是 resolved状态的回调函数,第二个参数(可选)是 rejected状态的回调函数。then方法返回一个新 Promise实例,与原来 Promise实例不同,因此可以使用链式写法,上一个 then的结果作为下一个 then的参数。

getJSON("/posts.json").then(function(json) {	
  return json.post;	
}).then(function(post) {	
  // ...	
});

12.4 Promise.prototype.catch()

Promise.prototype.catch方法是 .then(null,rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) {	
  // ...	
}).catch(function(error) {	
  // 处理 getJSON 和 前一个回调函数运行时发生的错误	
  console.log('发生错误!', error);	
});

如果 Promise 状态已经变成 resolved,再抛出错误是无效的。

const p = new Promise(function(resolve, reject) {	
  resolve('ok');	
  throw new Error('test');	
});	
p	
  .then(function(value) { console.log(value) })	
  .catch(function(error) { console.log(error) });	
// ok

promise抛出一个错误,就被 catch方法指定的回调函数捕获,下面三种写法相同。

// 写法一	
const p = new Promise(function(resolve, reject) {	
  throw new Error('test');	
});	
p.catch(function(error) {	
  console.log(error);	
});	
// Error: test	
// 写法二	
const p = new Promise(function(resolve, reject) {	
  try {	
    throw new Error('test');	
  } catch(e) {	
    reject(e);	
  }	
});	
p.catch(function(error) {	
  console.log(error);	
});	
// 写法三	
const p = new Promise(function(resolve, reject) {	
  reject(new Error('test'));	
});	
p.catch(function(error) {	
  console.log(error);	
});

一般来说,不要在 then方法里面定义 Reject 状态的回调函数(即 then的第二个参数),总是使用 catch方法。

// bad	
promise	
  .then(function(data) {	
    // success	
  }, function(err) {	
    // error	
  });	
// good	
promise	
  .then(function(data) { //cb	
    // success	
  })	
  .catch(function(err) {	
    // error	
  });

12.5 Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

promise	
.then(result => {···})	
.catch(error => {···})	
.finally(() => {···});

finally不接收任何参数,与状态无关,本质上是 then方法的特例。

promise	
.finally(() => {	
  // 语句	
});	
// 等同于	
promise	
.then(	
  result => {	
    // 语句	
    return result;	
  },	
  error => {	
    // 语句	
    throw error;	
  }	
);

上面代码中,如果不使用 finally方法,同样的语句需要为成功和失败两种情况各写一次。有了 finally方法,则只需要写一次。finally方法总是会返回原来的值。

// resolve 的值是 undefined	
Promise.resolve(2).then(() => {}, () => {})	
// resolve 的值是 2	
Promise.resolve(2).finally(() => {})	
// reject 的值是 undefined	
Promise.reject(3).then(() => {}, () => {})	
// reject 的值是 3	
Promise.reject(3).finally(() => {})

12.6 Promise.all()

用于将多个 Promise 实例,包装成一个新的 Promise 实例,参数可以不是数组,但必须是Iterator接口,且返回的每个成员都是 Promise实例。

const p = Promise.all([p1, p2, p3]);

p的状态由 p1p2p3决定,分成两种情况。

  1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

  2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

// 生成一个Promise对象的数组	
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {	
  return getJSON('/post/' + id + ".json");	
});	
Promise.all(promises).then(function (posts) {	
  // ...	
}).catch(function(reason){	
  // ...	
});

上面代码中, promises是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成 fulfilled,或者其中有一个变为 rejected,才会调用 Promise.all方法后面的回调函数。

注意:如果 Promise的参数中定义了 catch方法,则 rejected后不会触发 Promise.all()catch方法,因为参数中的 catch方法执行完后也会变成 resolved,当 Promise.all()方法参数的实例都是 resolved时就会调用 Promise.all()then方法。

const p1 = new Promise((resolve, reject) => {	
  resolve('hello');	
})	
.then(result => result)	
.catch(e => e);	
const p2 = new Promise((resolve, reject) => {	
  throw new Error('报错了');	
})	
.then(result => result)	
.catch(e => e);	
Promise.all([p1, p2])	
.then(result => console.log(result))	
.catch(e => console.log(e));	
// ["hello", Error: 报错了]

如果参数里面都没有catch方法,就会调用Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) => {	
  resolve('hello');	
})	
.then(result => result);	
const p2 = new Promise((resolve, reject) => {	
  throw new Error('报错了');	
})	
.then(result => result);	
Promise.all([p1, p2])	
.then(result => console.log(result))	
.catch(e => console.log(e));	
// Error: 报错了

12.7 Promise.race()

Promise.all方法类似,也是将多个 Promise实例包装成一个新的 Promise实例。

const p = Promise.race([p1, p2, p3]);

Promise.all方法区别在于, Promise.race方法是 p1, p2, p3中只要一个参数先改变状态,就会把这个参数的返回值传给 p的回调函数。

12.8 Promise.resolve()

将现有对象转换成 Promise 对象。

const p = Promise.resolve($.ajax('/whatever.json'));

12.9 Promise.reject()

返回一个 rejected状态的 Promise实例。

const p = Promise.reject('出错了');	
// 等同于	
const p = new Promise((resolve, reject) => reject('出错了'))	
p.then(null, function (s) {	
  console.log(s)	
});	
// 出错了

注意, Promise.reject()方法的参数,会原封不动地作为 reject的理由,变成后续方法的参数。这一点与 Promise.resolve方法不一致。

const thenable = {	
  then(resolve, reject) {	
    reject('出错了');	
  }	
};	
Promise.reject(thenable)	
.catch(e => {	
  console.log(e === thenable)	
})	
// true

13 Iterator和 for...of循环

13.1 Iterator遍历器概念

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator三个作用

  • 为各种数据结构,提供一个统一的、简便的访问接口;

  • 使得数据结构的成员能够按某种次序排列;

  • Iterator 接口主要供ES6新增的 for...of消费;

13.2 Iterator遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

  2. 第一次调用指针对象的 next方法,可以将指针指向数据结构的第一个成员。

  3. 第二次调用指针对象的 next方法,指针就指向数据结构的第二个成员。

  4. 不断调用指针对象的 next方法,直到它指向数据结构的结束位置。

每一次调用 next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 valuedone两个属性的对象。

  • value属性是当前成员的值;

  • done属性是一个布尔值,表示遍历是否结束;

模拟 next方法返回值:

let f = function (arr){	
    var nextIndex = 0;	
    return {	
        next:function(){	
            return nextIndex < arr.length ?	
            {value: arr[nextIndex++], done: false}:	
            {value: undefined, done: true}	
        }	
    }	
}	
let a = f(['a', 'b']);	
a.next(); // { value: "a", done: false }	
a.next(); // { value: "b", done: false }	
a.next(); // { value: undefined, done: true }

13.3 默认Iterator接口

若数据可遍历,即一种数据部署了Iterator接口。Symbol.iterator属性,即如果一个数据结构具有 Symbol.iterator属性,就可以认为是可遍历Symbol.iterator属性本身是函数,是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名 Symbol.iterator,它是一个表达式,返回 Symbol对象的 iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见《Symbol》一章)。

原生具有Iterator接口的数据结构有

  • Array

  • Map

  • Set

  • String

  • TypedArray

  • 函数的 arguments 对象

  • NodeList 对象

13.4 Iterator使用场景

  • (1)解构赋值Set 结构进行解构赋值时,会默认调用 Symbol.iterator方法。

let a = new Set().add('a').add('b').add('c');	
let [x, y] = a;       // x = 'a'  y = 'b'	
let [a1, ...a2] = a;  // a1 = 'a' a2 = ['b','c']
  • (2)扩展运算符...)也会调用默认的 Iterator 接口。

let a = 'hello';	
[...a];            //  ['h','e','l','l','o']	
let a = ['b', 'c'];	
['a', ...a, 'd'];  // ['a', 'b', 'c', 'd']
  • (2)yield*yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let a = function*(){	
    yield 1;	
    yield* [2,3,4];	
    yield 5;	
}	
let b = a();	
b.next() // { value: 1, done: false }	
b.next() // { value: 2, done: false }	
b.next() // { value: 3, done: false }	
b.next() // { value: 4, done: false }	
b.next() // { value: 5, done: false }	
b.next() // { value: undefined, done: true }
  • (4)其他场合

  • for...of

  • Array.from()

  • Map(), Set(), WeakMap(), WeakSet()(比如 newMap([['a',1],['b',2]])

  • Promise.all()

  • Promise.race()

13.5 for...of循环

只要数据结构部署了 Symbol.iterator属性,即具有 iterator 接口,可以用 for...of循环遍历它的成员。也就是说, for...of循环内部调用的是数据结构的 Symbol.iterato方法。使用场景for...of可以使用在数组 SetMap结构类数组对象Genetator对象字符串

  • 数组for...of循环可以代替数组实例的 forEach方法。

let a = ['a', 'b', 'c'];	
for (let k of a){console.log(k)}; // a b c	
a.forEach((ele, index)=>{	
    console.log(ele);    // a b c	
    console.log(index);  // 0 1 2 	
})

for...in对比, for...in只能获取对象键名,不能直接获取键值,而 for...of允许直接获取键值。

let a = ['a', 'b', 'c'];	
for (let k of a){console.log(k)};  // a b c	
for (let k in a){console.log(k)};  // 0 1 2
  • Set和Mapfor(let[k,v]of b){...}

let a = new Set(['a', 'b', 'c']);	
for (let k of a){console.log(k)}; // a b c	
let b = new Map();	
b.set('name','leo');	
b.set('age', 18);	
b.set('aaa','bbb');	
for (let [k,v] of b){console.log(k + ":" + v)};	
// name:leo	
// age:18	
// aaa:bbb
  • 类数组对象

// 字符串	
let a = 'hello';	
for (let k of a ){console.log(k)}; // h e l l o	
// DOM NodeList对象	
let b = document.querySelectorAll('p');	
for (let k of b ){	
    k.classList.add('test');	
}	
// arguments对象	
function f(){	
    for (let k of arguments){	
        console.log(k);	
    }	
}	
f('a','b'); // a b
  • 对象for...of会报错,要部署Iterator才能使用。

let a = {a:'aa',b:'bb',c:'cc'};	
for (let k in a){console.log(k)}; // a b c	
for (let k of a){console>log(k)}; // TypeError

13.6 跳出for...of

使用 break来实现。

for (let k of a){	
    if(k>100)	
        break;	
    console.log(k);	
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章