简介

在ES6中,引入了同步iteration的概念,随着ES8中的Async操作符的援用,是不是能够在一异步操作中进行遍历操作呢?

明天要给大家讲一讲ES9中的异步遍历的新个性Async iteration。

异步遍历

在解说异步遍历之前,咱们先回忆一下ES6中的同步遍历。

依据ES6的定义,iteration次要由三局部组成:

  1. Iterable

先看下Iterable的定义:

interface Iterable {    [Symbol.iterator]() : Iterator;}

Iterable示意这个对象外面有可遍历的数据,并且须要实现一个能够生成Iterator的工厂办法。

  1. Iterator
interface Iterator {    next() : IteratorResult;}

能够从Iterable中构建Iterator。Iterator是一个相似游标的概念,能够通过next拜访到IteratorResult。

  1. 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引入了异步遍历的概念:

  1. 能够通过Symbol.asyncIterator来获取到异步iterables中的iterator。
  2. 异步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的博客

欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!