Promise Iterator Generator async-await 都是异步解决方案,Iterator Generator async-await之间有千丝万缕的联系,起初从Iterator开始向后理解,感觉不好理解,于是我觉得倒着理解,从常用的async-await到其背后演变过程。
全文参考阮一峰老师的ES6 入门教程
一、Promise
Promise比较简单,基本用法如下:
const promise = new Promise((resolve, reject) => {
if(....){
resolve(value)
}else{
reject(error)
}
})
promise.then(() => {
// 成功走这里
}).catch(() => {
// 失败走这里
}).finallly(() => {
// 无论最后状态如何,都会执行
})
Promise.resolve()
Promise.reject()
const p = Promise.all([p1, p2, p3]);
p.then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
const p = Promise.race([p1, p2, p3]);
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。但是,如果p1被rejected,而且p1有自己的catch方法,则p不会走catch,此时p1执行catch后会变成resolved
Promise.race()则是p1,p2,p3中其中一个状态改变,p的状态就改变,那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
二、async-await
async函数是Generator 函数的语法糖。async定义一个异步函数,这个函数内用await来调用其他异步函数,拿到结果,再继续执行下方代码。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数的返回值是 Promise 对象。async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
为防止await后的异步方法出错,可以使用try{}catch{}
的结构。
知道了async函数是Generator 函数的语法糖。那么Generator是什么呢?
三、Generator(生成器)
上方的例子用Generator写是这样的:
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,Generator 函数还是一个遍历器生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。yield表达式是暂停标志。必须调用遍历器对象的next方法,使得指针移向下一个状态。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
与 Iterator 接口的关系
任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身.
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
async函数对 Generator 函数的改进。
- 内置执行器。async函数的执行,与普通函数一样,但Generator 函数,需要调用next方法,或者用co模块,才能真正执行。
- 更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值。
- 返回值是 Promise。async函数的返回值是 Promise 对象, Generator 函数的返回值是 Iterator。async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
for...of 循环
for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。return语句返回的值,不包括在for...of循环之中。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
前面提到,generator函数返回的是一个遍历器对象(Iterator Object),那么Iterator又是什么?
四、Iterator(遍历器)
概念
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
上面代码定义了一个makeIterator函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。它的执行过程是:
- 创建一个指针对象指向当前数据结构的其实位置。(遍历器对象本质上,就是一个指针对象。)
- 调用next()将指针指向第一个成员。继续不断调用next(),指向后续成员,知道指向最后一个成员。
默认 Iterator 接口
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
调用 Iterator 接口的场合
- 解构赋值。
let [first, ...rest] = ['a', 'b', 'c'];// first='a'; rest=['b','c'];
- 扩展运算符。
var str = 'hello'; [...str] // ['h','e','l','l','o']
- yield*。 generator函数中
yield * [1, 2, 3]
等
for...of循环
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。
一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
上面代码表明,for...in循环读取键名,for...of循环读取键值。