简介
在 ES6 中,引入了同步 iteration 的概念,随着 ES8 中的 Async 操作符的援用,是不是能够在一异步操作中进行遍历操作呢?
明天要给大家讲一讲 ES9 中的异步遍历的新个性 Async iteration。
异步遍历
在解说异步遍历之前,咱们先回忆一下 ES6 中的同步遍历。
依据 ES6 的定义,iteration 次要由三局部组成:
- Iterable
先看下 Iterable 的定义:
interface Iterable {[Symbol.iterator]() : Iterator;}
Iterable 示意这个对象外面有可遍历的数据,并且须要实现一个能够生成 Iterator 的工厂办法。
- Iterator
interface Iterator {next() : IteratorResult;
}
能够从 Iterable 中构建 Iterator。Iterator 是一个相似游标的概念,能够通过 next 拜访到 IteratorResult。
- IteratorResult
IteratorResult 是每次调用 next 办法失去的数据。
interface IteratorResult {
value: any;
done: boolean;
}
IteratorResult 中除了有一个 value 值示意要获取到的数据之外,还有一个 done,示意是否遍历实现。
上面是一个遍历数组的例子:
> const iterable = ['a', 'b'];
> const iterator = iterable[Symbol.iterator]();
> iterator.next()
{value: 'a', done: false}
> iterator.next()
{value: 'b', done: false}
> iterator.next()
{value: undefined, done: true}
然而上的例子遍历的是同步数据,如果咱们获取的是异步数据,比方从 http 端下载下来的文件,咱们想要一行一行的对文件进行遍历。因为读取一行数据是异步操作,那么这就波及到了异步数据的遍历。
退出异步读取文件的办法是 readLinesFromFile,那么同步的遍历办法,对异步来说就不再实用了:
// 不再实用
for (const line of readLinesFromFile(fileName)) {console.log(line);
}
兴许你会想,咱们是不是能够把异步读取一行的操作封装在 Promise 中,而后用同步的形式去遍历呢?
想法很好,不过这种状况下,异步操作是否执行结束是无奈检测到的。所以办法并不可行。
于是 ES9 引入了异步遍历的概念:
- 能够通过 Symbol.asyncIterator 来获取到异步 iterables 中的 iterator。
- 异步 iterator 的 next() 办法返回 Promises 对象,其中蕴含 IteratorResults。
所以,咱们看下异步遍历的 API 定义:
interface AsyncIterable {[Symbol.asyncIterator]() : AsyncIterator;}
interface AsyncIterator {next() : Promise<IteratorResult>;
}
interface IteratorResult {
value: any;
done: boolean;
}
咱们看一个异步遍历的利用:
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}
});
其中 createAsyncIterable 将会把一个同步的 iterable 转换成一个异步的 iterable,咱们将会在上面一大节中看一下到底怎么生成的。
这里咱们次要关注一下 asyncIterator 的遍历操作。
因为 ES8 中引入了 Async 操作符,咱们也能够把下面的代码,应用 Async 函数重写:
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}
}
异步 iterable 的遍历
应用 for-of 能够遍历同步 iterable, 应用 for-await-of 能够遍历异步 iterable。
async function f() {for await (const x of createAsyncIterable(['a', 'b'])) {console.log(x);
}
}
// Output:
// a
// b
留神,await 须要放在 async 函数中才行。
如果咱们的异步遍历中出现异常,则能够在 for-await-of 中应用 try catch 来捕捉这个异样:
function createRejectingIterable() {
return {[Symbol.asyncIterator]() {return this;},
next() {return Promise.reject(new Error('Problem!'));
},
};
}
(async function () {
try {for await (const x of createRejectingIterable()) {console.log(x);
}
} catch (e) {console.error(e);
// Error: Problem!
}
})();
同步的 iterable 返回的是同步的 iterators,next 办法返回的是 {value, done}。
如果应用 for-await-of 则会将同步的 iterators 转换成为异步的 iterators。而后返回的值被转换成为了 Promise。
如果同步的 next 自身返回的 value 就是 Promise 对象,则异步的返回值还是同样的 promise。
也就是说会把:Iterable<Promise<T>>
转换成为 AsyncIterable<T>
, 如上面的例子所示:
async function main() {
const syncIterable = [Promise.resolve('a'),
Promise.resolve('b'),
];
for await (const x of syncIterable) {console.log(x);
}
}
main();
// Output:
// a
// b
下面的例子将同步的 Promise 转换成异步的 Promise。
async function main() {for await (const x of ['a', 'b']) {console.log(x);
}
}
main();
// Output:
// c
// d
下面的例子将同步的常量转换成为 Promise。能够看到两者的后果是一样的。
异步 iterable 的生成
回到下面的例子,咱们应用 createAsyncIterable(syncIterable) 将 syncIterable 转换成了 AsyncIterable。
咱们看下这个办法是怎么实现的:
async function* createAsyncIterable(syncIterable) {for (const elem of syncIterable) {yield elem;}
}
下面的代码中,咱们在一个一般的 generator function 后面加上 async,示意的是异步的 generator。
对于一般的 generator 来说,每次调用 next 办法的时候,都会返回一个 object {value,done},这个 object 对象是对 yield 值的封装。
对于一个异步的 generator 来说,每次调用 next 办法的时候,都会返回一个蕴含 object {value,done} 的 promise 对象。这个 object 对象是对 yield 值的封装。
因为返回的是 Promise 对象,所以咱们不须要期待异步执行的后果实现,就能够再次调用 next 办法。
咱们能够通过一个 Promise.all 来同时执行所有的异步 Promise 操作:
const asyncGenObj = createAsyncIterable(['a', 'b']);
const [{value:v1},{value:v2}] = await Promise.all([asyncGenObj.next(), asyncGenObj.next()]);
console.log(v1, v2); // a b
在 createAsyncIterable 中,咱们是从同步的 Iterable 中创立异步的 Iterable。
接下来咱们看下如何从异步的 Iterable 中创立异步的 Iterable。
从上一节咱们晓得,能够应用 for-await-of 来读取异步 Iterable 的数据,于是咱们能够这样用:
async function* prefixLines(asyncIterable) {for await (const line of asyncIterable) {yield '>' + line;}
}
在 generator 一文中,咱们讲到了在 generator 中调用 generator。也就是在一个生产器中通过应用 yield* 来调用另外一个生成器。
同样的,如果是在异步生成器中,咱们能够做同样的事件:
async function* gen1() {
yield 'a';
yield 'b';
return 2;
}
async function* gen2() {const result = yield* gen1();
// result === 2
}
(async function () {for await (const x of gen2()) {console.log(x);
}
})();
// Output:
// a
// b
如果在异步生成器中抛出异样,这个异样也会被封装在 Promise 中:
async function* asyncGenerator() {throw new Error('Problem!');
}
asyncGenerator().next()
.catch(err => console.log(err)); // Error: Problem!
异步办法和异步生成器
异步办法是应用 async function 申明的办法,它会返回一个 Promise 对象。
function 中的 return 或 throw 异样会作为返回的 Promise 中的 value。
(async function () {return 'hello';})()
.then(x => console.log(x)); // hello
(async function () {throw new Error('Problem!');
})()
.catch(x => console.error(x)); // Error: Problem!
异步生成器是应用 async function * 申明的办法。它会返回一个异步的 iterable。
通过调用 iterable 的 next 办法,将会返回一个 Promise。异步生成器中 yield 的值会用来填充 Promise 的值。如果在生成器中抛出了异样,同样会被 Promise 捕捉到。
async function* gen() {yield 'hello';}
const genObj = gen();
genObj.next().then(x => console.log(x));
// {value: 'hello', done: false}
本文作者:flydean 程序那些事
本文链接:http://www.flydean.com/es9-async-iteration/
本文起源:flydean 的博客
欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!