1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 10:22:23 +00:00

docs(async): async Iterator

This commit is contained in:
ruanyf 2016-10-12 22:22:23 +08:00
parent ddf6220289
commit e364ba968a
4 changed files with 239 additions and 12 deletions

View File

@ -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
```

View File

@ -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结构。

View File

@ -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`

View File

@ -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