异步 Generator 函数
就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
在语法上,异步 Generator 函数就是async
函数与 Generator 函数的结合。
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 函数处理同步操作和异步操作时,能够使用同一套接口。
// 同步 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 函数的例子。
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 函数的用法如下。
(async function () {
for await (const line of readLines(filePath)) {
console.log(line);
}
})()
异步 Generator 函数可以与for await...of
循环结合起来使用。
async function* prefixLines(asyncIterable) {
for await (const line of asyncIterable) {
yield '> ' + line;
}
}
异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的next
方法,会返回一个 Promise 对象,也就是说,跟在yield
命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,yield
命令后面是一个字符串,会被自动包装成一个 Promise 对象。
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()
以后,上面代码的执行顺序如下。
ag.next()
立刻返回一个 Promise 对象。asyncGenerator
函数开始执行,打印出Start
。await
命令返回一个 Promise 对象,asyncGenerator
函数停在这里。- A 处变成 fulfilled 状态,产生的值放入
result
变量,asyncGenerator
函数继续往下执行。 - 函数在 B 处的
yield
暂停执行,一旦yield
命令取到值,ag.next()
返回的那个 Promise 对象变成 fulfilled 状态。 ag.next()
后面的then
方法指定的回调函数开始执行。该回调函数的参数是一个对象{value, done}
,其中value
的值是yield
命令后面的那个表达式的值,done
的值是false
。
A 和 B 两行的作用类似于下面的代码。
return new Promise((resolve, reject) => {
fetchRandom()
.then(result => result.text())
.then(result => {
resolve({
value: 'Result: ' + result,
done: false,
});
});
});
如果异步 Generator 函数抛出错误,会导致 Promise 对象的状态变为reject
,然后抛出的错误被catch
方法捕获。
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 函数的执行器。
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
,就会跳出循环,异步遍历器执行结束。
下面是这个自动执行器的一个使用实例。
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
方法的参数,接收外部传入的数据。
const writer = openFile('someFile.txt');
writer.next('hello'); // 立即执行
writer.next('world'); // 立即执行
await writer.return(); // 等待写入结束
上面代码中,openFile
是一个异步 Generator 函数。next
方法的参数,向该函数内部的操作传入数据。每次next
方法都是同步执行的,最后的await
命令用于等待整个写入操作结束。
最后,同步的数据结构,也可以使用异步 Generator 函数。
async function* createAsyncIterable(syncIterable) {
for (const elem of syncIterable) {
yield elem;
}
}
上面代码中,由于没有异步操作,所以也就没有使用await
关键字。