mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 18:32:22 +00:00
docs(async): async Iterator
This commit is contained in:
parent
ddf6220289
commit
e364ba968a
221
docs/async.md
221
docs/async.md
@ -1254,9 +1254,9 @@ async function chainAnimationsAsync(elem, animations) {
|
|||||||
|
|
||||||
## 异步遍历器
|
## 异步遍历器
|
||||||
|
|
||||||
《遍历器》一章说过,Iterator接口是一种数据遍历的协议,只要调用遍历器的`next`方法,就会得到一个对象值`{value, done}`。其中,`value`表示当前的数据的值,`done`是一个布尔值,表示遍历是否结束。
|
《遍历器》一章说过,Iterator接口是一种数据遍历的协议,只要调用遍历器对象的`next`方法,就会得到一个表示当前成员信息的对象`{value, done}`。其中,`value`表示当前的数据的值,`done`是一个布尔值,表示遍历是否结束。
|
||||||
|
|
||||||
这意味着,`next`方法是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`next`方法,就必须同步地得到`value`和`done`这两方面的信息。这对于同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator函数里面的异步操作,返回一个Thunk函数或者Promise对象,即`value`属性是一个Thunk函数或者Promise对象,等待以后返回真正的值,而`done`属性则还是同步产生的。
|
这隐含着规定,`next`方法是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`next`方法,就必须同步地得到`value`和`done`这两方面的信息。这对于同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator函数里面的异步操作,返回一个Thunk函数或者Promise对象,即`value`属性是一个Thunk函数或者Promise对象,等待以后返回真正的值,而`done`属性则还是同步产生的。
|
||||||
|
|
||||||
目前,有一个[提案](https://github.com/tc39/proposal-async-iteration),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生,这称为”异步遍历器“(Async Iterator)。
|
目前,有一个[提案](https://github.com/tc39/proposal-async-iteration),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生,这称为”异步遍历器“(Async Iterator)。
|
||||||
|
|
||||||
@ -1276,17 +1276,104 @@ asyncIterator
|
|||||||
|
|
||||||
我们知道,一个对象的同步遍历器的接口,部署在`Symbol.iterator`属性上面。同样地,对象的异步遍历器接口,部署在`Symbol.asyncIterator`属性上面。不管是什么样的对象,只要它的`Symbol.asyncIterator`属性有值,就表示应该对它进行异步遍历。
|
我们知道,一个对象的同步遍历器的接口,部署在`Symbol.iterator`属性上面。同样地,对象的异步遍历器接口,部署在`Symbol.asyncIterator`属性上面。不管是什么样的对象,只要它的`Symbol.asyncIterator`属性有值,就表示应该对它进行异步遍历。
|
||||||
|
|
||||||
|
下面是一个异步遍历器的例子。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||||
|
const asyncIterator = someCollection[Symbol.asyncIterator]();
|
||||||
|
|
||||||
|
asyncIterator.next()
|
||||||
|
.then(iterResult1 => {
|
||||||
|
console.log(iterResult1); // { value: 'a', done: false }
|
||||||
|
return asyncIterator.next();
|
||||||
|
}).then(iterResult2 => {
|
||||||
|
console.log(iterResult2); // { value: 'b', done: false }
|
||||||
|
return asyncIterator.next();
|
||||||
|
}).then(iterResult3 => {
|
||||||
|
console.log(iterResult3); // { value: undefined, done: true }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,异步遍历器其实返回了两次值。第一次调用的时候,返回一个Promise对象;等到Promise对象`resolve`了,再返回一个表示当前数据成员信息的对象。这就是说,异步遍历器与同步遍历器最终行为是一致的,只是会先返回Promise对象,作为中介。
|
||||||
|
|
||||||
|
由于异步遍历器的`next`方法,返回的是一个Promise对象。因此,可以把它放在`await`命令后面。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function f() {
|
||||||
|
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||||
|
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||||
|
console.log(await asyncIterator.next());
|
||||||
|
// { value: 'a', done: false }
|
||||||
|
console.log(await asyncIterator.next());
|
||||||
|
// { value: 'b', done: false }
|
||||||
|
console.log(await asyncIterator.next());
|
||||||
|
// { value: undefined, done: true }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`next`方法用`await`处理以后,就不必使用`then`方法了。整个流程已经很接近同步处理了。
|
||||||
|
|
||||||
|
注意,异步遍历器的`next`方法是可以连续调用的,不必等到上一步产生的Promise对象`resolve`以后再调用。这种情况下,`next`方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的`next`方法放在`Promise.all`方法里面。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const asyncGenObj = createAsyncIterable(['a', 'b']);
|
||||||
|
const [{value: v1}, {value: v2}] = await Promise.all([
|
||||||
|
asyncGenObj.next(), asyncGenObj.next()
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(v1, v2); // a b
|
||||||
|
```
|
||||||
|
|
||||||
|
另一种用法是一次性调用所有的`next`方法,然后`await`最后一步操作。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const writer = openFile('someFile.txt');
|
||||||
|
writer.next('hello');
|
||||||
|
writer.next('world');
|
||||||
|
await writer.return();
|
||||||
|
```
|
||||||
|
|
||||||
### for await...of
|
### for await...of
|
||||||
|
|
||||||
前面介绍过,`for...of`循环用于遍历同步的Iterator接口。新引入的`for await...of`循环,则是用于遍历异步的Iterator接口。
|
前面介绍过,`for...of`循环用于遍历同步的Iterator接口。新引入的`for await...of`循环,则是用于遍历异步的Iterator接口。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
for await (const line of readLines(filePath)) {
|
async function f() {
|
||||||
console.log(line);
|
for await (const x of createAsyncIterable(['a', 'b'])) {
|
||||||
|
console.log(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// a
|
||||||
|
// b
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`createAsyncIterable()`返回一个异步遍历器,`for...of`循环自动调用这个遍历器的`next`方法,会得到一个Promise对象。`await`用来处理这个Promise对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。
|
||||||
|
|
||||||
|
如果`next`方法返回的Promise对象被`reject`,那么就要用`try...catch`捕捉。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function () {
|
||||||
|
try {
|
||||||
|
for await (const x of createRejectingIterable()) {
|
||||||
|
console.log(x);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,`readLines`函数返回一个异步遍历器,每次调用它的`next`方法,就会返回一个Promise对象。`await`表示等待这个Promise对象`resolve`,一旦完成,变量`line`就是Promise对象返回的`value`值。
|
注意,`for await...of`循环也可以用于同步遍历器。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
(async function () {
|
||||||
|
for await (const x of ['a', 'b']) {
|
||||||
|
console.log(x);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// a
|
||||||
|
// b
|
||||||
|
```
|
||||||
|
|
||||||
### 异步Generator函数
|
### 异步Generator函数
|
||||||
|
|
||||||
@ -1308,6 +1395,126 @@ async function* readLines(path) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,异步操作前面使用`await`关键字标明,`next`方法所在的中断之处使用`yield`关键字标明。
|
上面代码中,异步操作前面使用`await`关键字标明,即`await`后面的操作,应该返回Promise对象。凡是使用`yield`关键字的地方,就是`next`方法的停下来的地方,它后面的表达式的值(即`await file.readLine()`的值),会作为`next()`返回对象的`value`属性,这一点是于同步Generator函数一致的。
|
||||||
|
|
||||||
注意,普通的`async`函数返回的是一个Promise对象,而异步Generator函数返回的是一个异步Iterator对象。
|
可以像下面这样,使用上面代码定义的异步Generator函数。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
for await (const line of readLines(filePath)) {
|
||||||
|
console.log(line);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
异步Generator函数可以与`for await...of`循环结合起来使用。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function* prefixLines(asyncIterable) {
|
||||||
|
for await (const line of asyncIterable) {
|
||||||
|
yield '> ' + line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`yield`命令依然是立刻返回的,但是返回的是一个Promise对象。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function* asyncGenerator() {
|
||||||
|
console.log('Start');
|
||||||
|
const result = await doSomethingAsync(); // (A)
|
||||||
|
yield 'Result: '+ result; // (B)
|
||||||
|
console.log('Done');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,调用`next`方法以后,会在`B`处暂停执行,`yield`命令立刻返回一个Promise对象。这个Promise对象不同于`A`处`await`命令后面的那个Promise对象。主要有两点不同,一是`A`处的Promise对象`resolve`以后产生的值,会放入`result`变量;二是`B`处的Promise对象`resolve`以后产生的值,是表达式`'Result: ' + result`的值;二是`A`处的Promise对象一定先于`B`处的Promise对象`resolve`。
|
||||||
|
|
||||||
|
如果异步Generator函数抛出错误,会被Promise对象`reject`,然后抛出的错误被`catch`方法捕获。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function* asyncGenerator() {
|
||||||
|
throw new Error('Problem!');
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncGenerator()
|
||||||
|
.next()
|
||||||
|
.catch(err => console.log(err)); // Error: Problem!
|
||||||
|
```
|
||||||
|
|
||||||
|
注意,普通的`async`函数返回的是一个Promise对象,而异步Generator函数返回的是一个异步Iterator对象。基本上,可以这样理解,`async`函数和异步Generator函数,是封装异步操作的两种方法,都用来达到同一种目的。区别在于,前者自带执行器,后者通过`for await...of`执行,或者自己编写执行器。下面就是一个异步Generator函数的执行器。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function takeAsync(asyncIterable, count=Infinity) {
|
||||||
|
const result = [];
|
||||||
|
const iterator = asyncIterable[Symbol.asyncIterator]();
|
||||||
|
while (result.length < count) {
|
||||||
|
const {value,done} = await iterator.next();
|
||||||
|
if (done) break;
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,异步Generator函数产生的异步遍历器,会通过`while`循环自动执行,每当`await iterator.next()`完成,就会进入下一轮循环。
|
||||||
|
|
||||||
|
下面是这个自动执行器的一个使用实例。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function f() {
|
||||||
|
async function* gen() {
|
||||||
|
yield 'a';
|
||||||
|
yield 'b';
|
||||||
|
yield 'c';
|
||||||
|
}
|
||||||
|
|
||||||
|
return await takeAsync(gen());
|
||||||
|
}
|
||||||
|
|
||||||
|
f().then(function (result) {
|
||||||
|
console.log(result); // ['a', 'b', 'c']
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
异步Generator函数出现以后,JavaScript就有了四种函数形式:普通函数、`async`函数、Generator函数和异步Generator函数。请注意区分每种函数的不同之处。
|
||||||
|
|
||||||
|
最后,同步的数据结构,也可以使用异步Generator函数。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function* createAsyncIterable(syncIterable) {
|
||||||
|
for (const elem of syncIterable) {
|
||||||
|
yield elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,由于没有异步操作,所以也就没有使用`await`关键字。
|
||||||
|
|
||||||
|
### yield* 语句
|
||||||
|
|
||||||
|
`yield*`语句也可以跟一个异步遍历器。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function* gen1() {
|
||||||
|
yield 'a';
|
||||||
|
yield 'b';
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function* gen2() {
|
||||||
|
const result = yield* gen1();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`gen2`函数里面的`result`变量,最后的值是`2`。
|
||||||
|
|
||||||
|
与同步Generator函数一样,`for await...of`循环会展开`yield*`。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
(async function () {
|
||||||
|
for await (const x of gen2()) {
|
||||||
|
console.log(x);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// a
|
||||||
|
// b
|
||||||
|
```
|
||||||
|
@ -110,7 +110,26 @@ interface IterationResult {
|
|||||||
|
|
||||||
Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即`for...of`循环(详见下文)。当使用`for...of`循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。
|
Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即`for...of`循环(详见下文)。当使用`for...of`循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。
|
||||||
|
|
||||||
ES6规定,默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。调用`Symbol.iterator`方法,就会得到当前数据结构默认的遍历器生成函数。`Symbol.iterator`本身是一个表达式,返回Symbol对象的`iterator`属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内(请参考Symbol一章)。
|
一种数据结构只要部署了Iterator接口,我们就称这种数据结构是”可遍历的“(iterable)。
|
||||||
|
|
||||||
|
ES6规定,默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。`Symbol.iterator`属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名`Symbol.iterator`,它是一个表达式,返回`Symbol`对象的`iterator`属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内。(参见Symbol一章)。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const obj = {
|
||||||
|
[Symbol.iterator] : function () {
|
||||||
|
return {
|
||||||
|
next: function () {
|
||||||
|
return {
|
||||||
|
value: 1,
|
||||||
|
done: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,对象`obj`是可遍历的(iterable),因为具有`Symbol.iterator`属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有`next`方法。每次调用`next`方法,都会返回一个代表当前成员的信息对象,具有`value`和`done`两个属性。
|
||||||
|
|
||||||
在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。
|
在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。
|
||||||
|
|
||||||
|
@ -547,7 +547,7 @@ window.a // 2
|
|||||||
|
|
||||||
上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。
|
上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。
|
||||||
|
|
||||||
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了两个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错)。另一方面,`window`对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
|
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,`window`对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
|
||||||
|
|
||||||
ES6为了改变这一点,一方面规定,为了保持兼容性,`var`命令和`function`命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,`let`命令、`const`命令、`class`命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。
|
ES6为了改变这一点,一方面规定,为了保持兼容性,`var`命令和`function`命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,`let`命令、`const`命令、`class`命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。
|
||||||
|
|
||||||
@ -571,7 +571,7 @@ ES5的顶层对象,本身也是一个问题,因为它在各种实现里面
|
|||||||
- 浏览器和Web Worker里面,`self`也指向顶层对象,但是Node没有`self`。
|
- 浏览器和Web Worker里面,`self`也指向顶层对象,但是Node没有`self`。
|
||||||
- Node里面,顶层对象是`global`,但其他环境都不支持。
|
- Node里面,顶层对象是`global`,但其他环境都不支持。
|
||||||
|
|
||||||
为了能够在各种环境,都能取到顶层对象,现在一般是使用`this`变量。
|
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用`this`变量,但是有局限性。
|
||||||
|
|
||||||
- 全局环境中,`this`会返回顶层对象。但是,Node模块和ES6模块中,`this`返回的是当前模块。
|
- 全局环境中,`this`会返回顶层对象。但是,Node模块和ES6模块中,`this`返回的是当前模块。
|
||||||
- 函数里面的`this`,如果函数不是作为对象的方法运行,而是单纯作为函数运行,`this`会指向顶层对象。但是,严格模式下,这时`this`会返回`undefined`。
|
- 函数里面的`this`,如果函数不是作为对象的方法运行,而是单纯作为函数运行,`this`会指向顶层对象。但是,严格模式下,这时`this`会返回`undefined`。
|
||||||
@ -610,7 +610,7 @@ require('system.global/shim')();
|
|||||||
import shim from 'system.global/shim'; shim();
|
import shim from 'system.global/shim'; shim();
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码可以保证`global`对象存在。
|
上面代码可以保证各种环境里面,`global`对象都是存在的。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// CommonJS的写法
|
// CommonJS的写法
|
||||||
@ -621,5 +621,5 @@ import getGlobal from 'system.global';
|
|||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码将顶层对象放入一个变量。
|
上面代码将顶层对象放入变量`global`。
|
||||||
|
|
||||||
|
@ -167,6 +167,7 @@
|
|||||||
- Nolan Lawson, [Taming the asynchronous beast with ES7](http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html): async函数通俗的实例讲解
|
- Nolan Lawson, [Taming the asynchronous beast with ES7](http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html): async函数通俗的实例讲解
|
||||||
- Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对async与Generator混合使用的一些讨论
|
- Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对async与Generator混合使用的一些讨论
|
||||||
- Daniel Brain, [Understand promises before you start using async/await](https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8): 讨论async/await与Promise的关系
|
- Daniel Brain, [Understand promises before you start using async/await](https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8): 讨论async/await与Promise的关系
|
||||||
|
- Axel Rauschmayer, [ES proposal: asynchronous iteration](http://www.2ality.com/2016/10/asynchronous-iteration.html): 异步遍历器的详细介绍
|
||||||
|
|
||||||
## Class
|
## Class
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user