From 6545088030b47f5705f337822851430d70f9a1d5 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 8 Jun 2019 02:20:08 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=BC=82=E6=AD=A5=E9=81=8D=E5=8E=86?= =?UTF-8?q?=E5=99=A8=E7=8B=AC=E7=AB=8B=E6=88=90=E4=B8=80=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/async-iterator.md | 408 +++++++++++++++++++++++++++++++++++++++ docs/async.md | 424 ----------------------------------------- sidebar.md | 1 + 3 files changed, 409 insertions(+), 424 deletions(-) create mode 100644 docs/async-iterator.md diff --git a/docs/async-iterator.md b/docs/async-iterator.md new file mode 100644 index 0000000..15570be --- /dev/null +++ b/docs/async-iterator.md @@ -0,0 +1,408 @@ +# 异步遍历器 + +《遍历器》一章说过,Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的`next`方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。`next`方法返回的对象的结构是`{value, done}`,其中`value`表示当前的数据的值,`done`是一个布尔值,表示遍历是否结束。 + +这里隐含着一个规定,`next`方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`next`方法,就必须同步地得到`value`和`done`这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator 函数里面的异步操作,返回一个 Thunk 函数或者 Promise 对象,即`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。 + +ES2018 [引入](https://github.com/tc39/proposal-async-iteration)了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生。 + +## 异步遍历的接口 + +异步遍历器的最大的语法特点,就是调用遍历器的`next`方法,返回的是一个 Promise 对象。 + +```javascript +asyncIterator + .next() + .then( + ({ value, done }) => /* ... */ + ); +``` + +上面代码中,`asyncIterator`是一个异步遍历器,调用`next`方法以后,返回一个 Promise 对象。因此,可以使用`then`方法指定,这个 Promise 对象的状态变为`resolve`以后的回调函数。回调函数的参数,则是一个具有`value`和`done`两个属性的对象,这个跟同步遍历器是一样的。 + +我们知道,一个对象的同步遍历器的接口,部署在`Symbol.iterator`属性上面。同样地,对象的异步遍历器接口,部署在`Symbol.asyncIterator`属性上面。不管是什么样的对象,只要它的`Symbol.asyncIterator`属性有值,就表示应该对它进行异步遍历。 + +下面是一个异步遍历器的例子。 + +```javascript +const asyncIterable = createAsyncIterable(['a', 'b']); +const asyncIterator = asyncIterable[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 asyncIterable = createAsyncIterable(['a', 'b']); +const asyncIterator = asyncIterable[Symbol.asyncIterator](); +const [{value: v1}, {value: v2}] = await Promise.all([ + asyncIterator.next(), asyncIterator.next() +]); + +console.log(v1, v2); // a b +``` + +另一种用法是一次性调用所有的`next`方法,然后`await`最后一步操作。 + +```javascript +async function runner() { + const writer = openFile('someFile.txt'); + writer.next('hello'); + writer.next('world'); + await writer.return(); +} + +runner(); +``` + +## for await...of + +前面介绍过,`for...of`循环用于遍历同步的 Iterator 接口。新引入的`for await...of`循环,则是用于遍历异步的 Iterator 接口。 + +```javascript +async function f() { + for await (const x of createAsyncIterable(['a', 'b'])) { + console.log(x); + } +} +// a +// b +``` + +上面代码中,`createAsyncIterable()`返回一个拥有异步遍历器接口的对象,`for...of`循环自动调用这个对象的异步遍历器的`next`方法,会得到一个 Promise 对象。`await`用来处理这个 Promise 对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。 + +`for await...of`循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。 + +```javascript +let body = ''; + +async function f() { + for await(const data of req) body += data; + const parsed = JSON.parse(body); + console.log('got', parsed); +} +``` + +上面代码中,`req`是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用`for await...of`循环以后,代码会非常简洁。 + +如果`next`方法返回的 Promise 对象被`reject`,`for await...of`就会报错,要用`try...catch`捕捉。 + +```javascript +async function () { + try { + for await (const x of createRejectingIterable()) { + console.log(x); + } + } catch (e) { + console.error(e); + } +} +``` + +注意,`for await...of`循环也可以用于同步遍历器。 + +```javascript +(async function () { + for await (const x of ['a', 'b']) { + console.log(x); + } +})(); +// a +// b +``` + +Node v10 支持异步遍历器,Stream 就部署了这个接口。下面是读取文件的传统写法与异步遍历器写法的差异。 + +```javascript +// 传统写法 +function main(inputFilePath) { + const readStream = fs.createReadStream( + inputFilePath, + { encoding: 'utf8', highWaterMark: 1024 } + ); + readStream.on('data', (chunk) => { + console.log('>>> '+chunk); + }); + readStream.on('end', () => { + console.log('### DONE ###'); + }); +} + +// 异步遍历器写法 +async function main(inputFilePath) { + const readStream = fs.createReadStream( + inputFilePath, + { encoding: 'utf8', highWaterMark: 1024 } + ); + + for await (const chunk of readStream) { + console.log('>>> '+chunk); + } + console.log('### DONE ###'); +} +``` + +## 异步 Generator 函数 + +就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。 + +在语法上,异步 Generator 函数就是`async`函数与 Generator 函数的结合。 + +```javascript +async function* gen() { + yield 'hello'; +} +const genObj = gen(); +genObj.next().then(x => console.log(x)); +// { value: 'hello', done: false } +``` + +上面代码中,`gen`是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用`next`方法,返回一个 Promise 对象。 + +异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。 + +```javascript +// 同步 Generator 函数 +function* map(iterable, func) { + const iter = iterable[Symbol.iterator](); + while (true) { + const {value, done} = iter.next(); + if (done) break; + yield func(value); + } +} + +// 异步 Generator 函数 +async function* map(iterable, func) { + const iter = iterable[Symbol.asyncIterator](); + while (true) { + const {value, done} = await iter.next(); + if (done) break; + yield func(value); + } +} +``` + +上面代码中,`map`是一个 Generator 函数,第一个参数是可遍历对象`iterable`,第二个参数是一个回调函数`func`。`map`的作用是将`iterable`每一步返回的值,使用`func`进行处理。上面有两个版本的`map`,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。 + +下面是另一个异步 Generator 函数的例子。 + +```javascript +async function* readLines(path) { + let file = await fileOpen(path); + + try { + while (!file.EOF) { + yield await file.readLine(); + } + } finally { + await file.close(); + } +} +``` + +上面代码中,异步操作前面使用`await`关键字标明,即`await`后面的操作,应该返回 Promise 对象。凡是使用`yield`关键字的地方,就是`next`方法停下来的地方,它后面的表达式的值(即`await file.readLine()`的值),会作为`next()`返回对象的`value`属性,这一点是与同步 Generator 函数一致的。 + +异步 Generator 函数内部,能够同时使用`await`和`yield`命令。可以这样理解,`await`命令用于将外部操作产生的值输入函数内部,`yield`命令用于将函数内部的值输出。 + +上面代码定义的异步 Generator 函数的用法如下。 + +```javascript +(async function () { + 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; + } +} +``` + +异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的`next`方法,会返回一个 Promise 对象,也就是说,跟在`yield`命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,`yield`命令后面是一个字符串,会被自动包装成一个 Promise 对象。 + +```javascript +function fetchRandom() { + const url = 'https://www.random.org/decimal-fractions/' + + '?num=1&dec=10&col=1&format=plain&rnd=new'; + return fetch(url); +} + +async function* asyncGenerator() { + console.log('Start'); + const result = await fetchRandom(); // (A) + yield 'Result: ' + await result.text(); // (B) + console.log('Done'); +} + +const ag = asyncGenerator(); +ag.next().then(({value, done}) => { + console.log(value); +}) +``` + +上面代码中,`ag`是`asyncGenerator`函数返回的异步遍历器对象。调用`ag.next()`以后,上面代码的执行顺序如下。 + +1. `ag.next()`立刻返回一个 Promise 对象。 +1. `asyncGenerator`函数开始执行,打印出`Start`。 +1. `await`命令返回一个 Promise 对象,`asyncGenerator`函数停在这里。 +1. A 处变成 fulfilled 状态,产生的值放入`result`变量,`asyncGenerator`函数继续往下执行。 +1. 函数在 B 处的`yield`暂停执行,一旦`yield`命令取到值,`ag.next()`返回的那个 Promise 对象变成 fulfilled 状态。 +1. `ag.next()`后面的`then`方法指定的回调函数开始执行。该回调函数的参数是一个对象`{value, done}`,其中`value`的值是`yield`命令后面的那个表达式的值,`done`的值是`false`。 + +A 和 B 两行的作用类似于下面的代码。 + +```javascript +return new Promise((resolve, reject) => { + fetchRandom() + .then(result => result.text()) + .then(result => { + resolve({ + value: 'Result: ' + result, + done: false, + }); + }); +}); +``` + +如果异步 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()`完成,就会进入下一轮循环。一旦`done`属性变为`true`,就会跳出循环,异步遍历器执行结束。 + +下面是这个自动执行器的一个使用实例。 + +```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 函数。请注意区分每种函数的不同之处。基本上,如果是一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘),可以使用 async 函数;如果是一系列产生相同数据结构的异步操作(比如一行一行读取文件),可以使用异步 Generator 函数。 + +异步 Generator 函数也可以通过`next`方法的参数,接收外部传入的数据。 + +```javascript +const writer = openFile('someFile.txt'); +writer.next('hello'); // 立即执行 +writer.next('world'); // 立即执行 +await writer.return(); // 等待写入结束 +``` + +上面代码中,`openFile`是一个异步 Generator 函数。`next`方法的参数,向该函数内部的操作传入数据。每次`next`方法都是同步执行的,最后的`await`命令用于等待整个写入操作结束。 + +最后,同步的数据结构,也可以使用异步 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() { + // result 最终会等于 2 + 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 +``` + diff --git a/docs/async.md b/docs/async.md index 8e707a3..91c38b4 100644 --- a/docs/async.md +++ b/docs/async.md @@ -486,24 +486,6 @@ async function dbFuc(db) { } ``` -目前,[`esm`](https://www.npmjs.com/package/esm)模块加载器支持顶层`await`,即`await`命令可以不放在 async 函数里面,直接使用。 - -```javascript -// async 函数的写法 -const start = async () => { - const res = await fetch('google.com'); - return res.text(); -}; - -start().then(console.log); - -// 顶层 await 的写法 -const res = await fetch('google.com'); -console.log(await res.text()); -``` - -上面代码中,第二种写法的脚本必须使用`esm`加载器,才会生效。 - 第四点,async 函数可以保留运行堆栈。 ```javascript @@ -702,410 +684,4 @@ async function logInOrder(urls) { 上面代码中,虽然`map`方法的参数是`async`函数,但它是并发执行的,因为只有`async`函数内部是继发执行,外部不受影响。后面的`for..of`循环内部使用了`await`,因此实现了按顺序输出。 -## 异步遍历器 -《遍历器》一章说过,Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的`next`方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。`next`方法返回的对象的结构是`{value, done}`,其中`value`表示当前的数据的值,`done`是一个布尔值,表示遍历是否结束。 - -这里隐含着一个规定,`next`方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`next`方法,就必须同步地得到`value`和`done`这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator 函数里面的异步操作,返回一个 Thunk 函数或者 Promise 对象,即`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。 - -ES2018 [引入](https://github.com/tc39/proposal-async-iteration)了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生。 - -### 异步遍历的接口 - -异步遍历器的最大的语法特点,就是调用遍历器的`next`方法,返回的是一个 Promise 对象。 - -```javascript -asyncIterator - .next() - .then( - ({ value, done }) => /* ... */ - ); -``` - -上面代码中,`asyncIterator`是一个异步遍历器,调用`next`方法以后,返回一个 Promise 对象。因此,可以使用`then`方法指定,这个 Promise 对象的状态变为`resolve`以后的回调函数。回调函数的参数,则是一个具有`value`和`done`两个属性的对象,这个跟同步遍历器是一样的。 - -我们知道,一个对象的同步遍历器的接口,部署在`Symbol.iterator`属性上面。同样地,对象的异步遍历器接口,部署在`Symbol.asyncIterator`属性上面。不管是什么样的对象,只要它的`Symbol.asyncIterator`属性有值,就表示应该对它进行异步遍历。 - -下面是一个异步遍历器的例子。 - -```javascript -const asyncIterable = createAsyncIterable(['a', 'b']); -const asyncIterator = asyncIterable[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 asyncIterable = createAsyncIterable(['a', 'b']); -const asyncIterator = asyncIterable[Symbol.asyncIterator](); -const [{value: v1}, {value: v2}] = await Promise.all([ - asyncIterator.next(), asyncIterator.next() -]); - -console.log(v1, v2); // a b -``` - -另一种用法是一次性调用所有的`next`方法,然后`await`最后一步操作。 - -```javascript -async function runner() { - const writer = openFile('someFile.txt'); - writer.next('hello'); - writer.next('world'); - await writer.return(); -} - -runner(); -``` - -### for await...of - -前面介绍过,`for...of`循环用于遍历同步的 Iterator 接口。新引入的`for await...of`循环,则是用于遍历异步的 Iterator 接口。 - -```javascript -async function f() { - for await (const x of createAsyncIterable(['a', 'b'])) { - console.log(x); - } -} -// a -// b -``` - -上面代码中,`createAsyncIterable()`返回一个拥有异步遍历器接口的对象,`for...of`循环自动调用这个对象的异步遍历器的`next`方法,会得到一个 Promise 对象。`await`用来处理这个 Promise 对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。 - -`for await...of`循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。 - -```javascript -let body = ''; - -async function f() { - for await(const data of req) body += data; - const parsed = JSON.parse(body); - console.log('got', parsed); -} -``` - -上面代码中,`req`是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用`for await...of`循环以后,代码会非常简洁。 - -如果`next`方法返回的 Promise 对象被`reject`,`for await...of`就会报错,要用`try...catch`捕捉。 - -```javascript -async function () { - try { - for await (const x of createRejectingIterable()) { - console.log(x); - } - } catch (e) { - console.error(e); - } -} -``` - -注意,`for await...of`循环也可以用于同步遍历器。 - -```javascript -(async function () { - for await (const x of ['a', 'b']) { - console.log(x); - } -})(); -// a -// b -``` - -Node v10 支持异步遍历器,Stream 就部署了这个接口。下面是读取文件的传统写法与异步遍历器写法的差异。 - -```javascript -// 传统写法 -function main(inputFilePath) { - const readStream = fs.createReadStream( - inputFilePath, - { encoding: 'utf8', highWaterMark: 1024 } - ); - readStream.on('data', (chunk) => { - console.log('>>> '+chunk); - }); - readStream.on('end', () => { - console.log('### DONE ###'); - }); -} - -// 异步遍历器写法 -async function main(inputFilePath) { - const readStream = fs.createReadStream( - inputFilePath, - { encoding: 'utf8', highWaterMark: 1024 } - ); - - for await (const chunk of readStream) { - console.log('>>> '+chunk); - } - console.log('### DONE ###'); -} -``` - -### 异步 Generator 函数 - -就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。 - -在语法上,异步 Generator 函数就是`async`函数与 Generator 函数的结合。 - -```javascript -async function* gen() { - yield 'hello'; -} -const genObj = gen(); -genObj.next().then(x => console.log(x)); -// { value: 'hello', done: false } -``` - -上面代码中,`gen`是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用`next`方法,返回一个 Promise 对象。 - -异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。 - -```javascript -// 同步 Generator 函数 -function* map(iterable, func) { - const iter = iterable[Symbol.iterator](); - while (true) { - const {value, done} = iter.next(); - if (done) break; - yield func(value); - } -} - -// 异步 Generator 函数 -async function* map(iterable, func) { - const iter = iterable[Symbol.asyncIterator](); - while (true) { - const {value, done} = await iter.next(); - if (done) break; - yield func(value); - } -} -``` - -上面代码中,`map`是一个 Generator 函数,第一个参数是可遍历对象`iterable`,第二个参数是一个回调函数`func`。`map`的作用是将`iterable`每一步返回的值,使用`func`进行处理。上面有两个版本的`map`,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。 - -下面是另一个异步 Generator 函数的例子。 - -```javascript -async function* readLines(path) { - let file = await fileOpen(path); - - try { - while (!file.EOF) { - yield await file.readLine(); - } - } finally { - await file.close(); - } -} -``` - -上面代码中,异步操作前面使用`await`关键字标明,即`await`后面的操作,应该返回 Promise 对象。凡是使用`yield`关键字的地方,就是`next`方法停下来的地方,它后面的表达式的值(即`await file.readLine()`的值),会作为`next()`返回对象的`value`属性,这一点是与同步 Generator 函数一致的。 - -异步 Generator 函数内部,能够同时使用`await`和`yield`命令。可以这样理解,`await`命令用于将外部操作产生的值输入函数内部,`yield`命令用于将函数内部的值输出。 - -上面代码定义的异步 Generator 函数的用法如下。 - -```javascript -(async function () { - 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; - } -} -``` - -异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的`next`方法,会返回一个 Promise 对象,也就是说,跟在`yield`命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,`yield`命令后面是一个字符串,会被自动包装成一个 Promise 对象。 - -```javascript -function fetchRandom() { - const url = 'https://www.random.org/decimal-fractions/' - + '?num=1&dec=10&col=1&format=plain&rnd=new'; - return fetch(url); -} - -async function* asyncGenerator() { - console.log('Start'); - const result = await fetchRandom(); // (A) - yield 'Result: ' + await result.text(); // (B) - console.log('Done'); -} - -const ag = asyncGenerator(); -ag.next().then(({value, done}) => { - console.log(value); -}) -``` - -上面代码中,`ag`是`asyncGenerator`函数返回的异步遍历器对象。调用`ag.next()`以后,上面代码的执行顺序如下。 - -1. `ag.next()`立刻返回一个 Promise 对象。 -1. `asyncGenerator`函数开始执行,打印出`Start`。 -1. `await`命令返回一个 Promise 对象,`asyncGenerator`函数停在这里。 -1. A 处变成 fulfilled 状态,产生的值放入`result`变量,`asyncGenerator`函数继续往下执行。 -1. 函数在 B 处的`yield`暂停执行,一旦`yield`命令取到值,`ag.next()`返回的那个 Promise 对象变成 fulfilled 状态。 -1. `ag.next()`后面的`then`方法指定的回调函数开始执行。该回调函数的参数是一个对象`{value, done}`,其中`value`的值是`yield`命令后面的那个表达式的值,`done`的值是`false`。 - -A 和 B 两行的作用类似于下面的代码。 - -```javascript -return new Promise((resolve, reject) => { - fetchRandom() - .then(result => result.text()) - .then(result => { - resolve({ - value: 'Result: ' + result, - done: false, - }); - }); -}); -``` - -如果异步 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()`完成,就会进入下一轮循环。一旦`done`属性变为`true`,就会跳出循环,异步遍历器执行结束。 - -下面是这个自动执行器的一个使用实例。 - -```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 函数。请注意区分每种函数的不同之处。基本上,如果是一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘),可以使用 async 函数;如果是一系列产生相同数据结构的异步操作(比如一行一行读取文件),可以使用异步 Generator 函数。 - -异步 Generator 函数也可以通过`next`方法的参数,接收外部传入的数据。 - -```javascript -const writer = openFile('someFile.txt'); -writer.next('hello'); // 立即执行 -writer.next('world'); // 立即执行 -await writer.return(); // 等待写入结束 -``` - -上面代码中,`openFile`是一个异步 Generator 函数。`next`方法的参数,向该函数内部的操作传入数据。每次`next`方法都是同步执行的,最后的`await`命令用于等待整个写入操作结束。 - -最后,同步的数据结构,也可以使用异步 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() { - // result 最终会等于 2 - 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 -``` diff --git a/sidebar.md b/sidebar.md index ce32f80..0e49041 100644 --- a/sidebar.md +++ b/sidebar.md @@ -26,6 +26,7 @@ 1. [Generator 函数的语法](#docs/generator) 1. [Generator 函数的异步应用](#docs/generator-async) 1. [async 函数](#docs/async) +1. [异步遍历器](#docs/async-iterator) 1. [Class 的基本语法](#docs/class) 1. [Class 的继承](#docs/class-extends) 1. [Module 的语法](#docs/module)