Home >Web Front-end >JS Tutorial >Detailed explanation of the new feature Async iteration in ES9

Detailed explanation of the new feature Async iteration in ES9

青灯夜游
青灯夜游forward
2021-04-20 09:49:062500browse

Detailed explanation of the new feature Async iteration in ES9

In ES6, the concept of synchronous iteration was introduced. With the reference of the Async operator in ES8, is it possible to perform traversal operations in an asynchronous operation?

Today I want to tell you about the new feature of asynchronous traversal in ES9, Async iteration.

Asynchronous traversal


Before explaining asynchronous traversal, let’s first recall the synchronous traversal in ES6.

According to the definition of ES6, iteration mainly consists of three parts:

1. Iterable

Let’s first look at the definition of Iterable:

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

Iterable means that there is traversable data in this object, and a factory method that can generate an Iterator needs to be implemented.

2. Iterator

interface Iterator {
    next() : IteratorResult;
}

Iterator can be constructed from Iterable. Iterator is a cursor-like concept, and IteratorResult can be accessed through next.

3. IteratorResult

IteratorResult is the data obtained each time the next method is called.

interface IteratorResult {
    value: any;
    done: boolean;
}

In addition to a value indicating the data to be obtained, IteratorResult also has a done indicating whether the traversal is completed.

The following is an example of traversing an array:

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

But the above example traverses synchronous data. If we obtain asynchronous data, such as a file downloaded from the http side, we want to To traverse the file line by line. Because reading a row of data is an asynchronous operation, this involves asynchronous data traversal.

The method to add asynchronous reading of files is readLinesFromFile, so the synchronous traversal method is no longer applicable to asynchronous:

//不再适用
for (const line of readLinesFromFile(fileName)) {
    console.log(line);
}

Maybe you will think, can we use asynchronous How about encapsulating the operation of reading a row in a Promise and then traversing it synchronously?

The idea is good, but in this case, it is impossible to detect whether the asynchronous operation is completed. So the method is not feasible.

So ES9 introduced the concept of asynchronous traversal:

  • The iterator in asynchronous iterables can be obtained through Symbol.asyncIterator.

  • The next() method of asynchronous iterator returns a Promises object, which contains IteratorResults.

So, let’s look at the API definition of asynchronous traversal:

interface AsyncIterable {
    [Symbol.asyncIterator]() : AsyncIterator;
}
interface AsyncIterator {
    next() : Promise<IteratorResult>;
}
interface IteratorResult {
    value: any;
    done: boolean;
}

Let’s look at an asynchronous traversal application:

const asyncIterable = createAsyncIterable([&#39;a&#39;, &#39;b&#39;]);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator.next()
.then(iterResult1 => {
    console.log(iterResult1); // { value: &#39;a&#39;, done: false }
    return asyncIterator.next();
})
.then(iterResult2 => {
    console.log(iterResult2); // { value: &#39;b&#39;, done: false }
    return asyncIterator.next();
})
.then(iterResult3 => {
    console.log(iterResult3); // { value: undefined, done: true }
});

where createAsyncIterable will A synchronous iterable is converted into an asynchronous iterable. We will take a look at how it is generated in the following section.

Here we mainly focus on the traversal operation of asyncIterator.

Because the Async operator was introduced in ES8, we can also rewrite the above code using the Async function:

async function f() {
    const asyncIterable = createAsyncIterable([&#39;a&#39;, &#39;b&#39;]);
    const asyncIterator = asyncIterable[Symbol.asyncIterator]();
    console.log(await asyncIterator.next());
        // { value: &#39;a&#39;, done: false }
    console.log(await asyncIterator.next());
        // { value: &#39;b&#39;, done: false }
    console.log(await asyncIterator.next());
        // { value: undefined, done: true }
}

Asynchronous iterable traversal


Use for-of to traverse synchronous iterable, and use for-await-of to traverse asynchronous iterable.

async function f() {
    for await (const x of createAsyncIterable([&#39;a&#39;, &#39;b&#39;])) {
        console.log(x);
    }
}
// Output:
// a
// b

Note that await needs to be placed in the async function.

If an exception occurs during our asynchronous traversal, you can use try catch in for-await-of to catch the exception:

function createRejectingIterable() {
    return {
        [Symbol.asyncIterator]() {
            return this;
        },
        next() {
            return Promise.reject(new Error(&#39;Problem!&#39;));
        },
    };
}
(async function () { 
    try {
        for await (const x of createRejectingIterable()) {
            console.log(x);
        }
    } catch (e) {
        console.error(e);
            // Error: Problem!
    }
})();

Synchronized iterable returns synchronous iterators, next The method returns {value, done}.

If you use for-await-of, synchronous iterators will be converted into asynchronous iterators. The returned value is then converted into a Promise.

If the value returned by the synchronized next itself is a Promise object, the asynchronous return value is still the same promise.

That is to say, it will convert: Iterableea14b2e011575f0c7465d02dc55e095c> into AsyncIterable8742468051c85b06f0a0af9e3e506b5c, as shown in the following example:

async function main() {
    const syncIterable = [
        Promise.resolve(&#39;a&#39;),
        Promise.resolve(&#39;b&#39;),
    ];
    for await (const x of syncIterable) {
        console.log(x);
    }
}
main();

// Output:
// a
// b

The above example converts a synchronous Promise into an asynchronous Promise.

async function main() {
    for await (const x of [&#39;a&#39;, &#39;b&#39;]) {
        console.log(x);
    }
}
main();

// Output:
// c
// d

The above example converts synchronized constants into Promise. It can be seen that the results of both are the same.

Generation of asynchronous iterable


Going back to the above example, we use createAsyncIterable(syncIterable) to convert syncIterable into AsyncIterable.

Let’s see how this method is implemented:

async function* createAsyncIterable(syncIterable) {
    for (const elem of syncIterable) {
        yield elem;
    }
}

In the above code, we add async in front of an ordinary generator function, which represents an asynchronous generator.

For ordinary generators, every time the next method is called, an object {value,done} will be returned. This object object encapsulates the yield value.

For an asynchronous generator, every time the next method is called, a promise object containing object {value,done} will be returned. This object object is an encapsulation of the yield value.

Because a Promise object is returned, we do not need to wait for the result of asynchronous execution to complete before calling the next method again.

We can use a Promise.all to perform all asynchronous Promise operations at the same time:

const asyncGenObj = createAsyncIterable([&#39;a&#39;, &#39;b&#39;]);
const [{value:v1},{value:v2}] = await Promise.all([
    asyncGenObj.next(), asyncGenObj.next()
]);
console.log(v1, v2); // a b

In createAsyncIterable, we create an asynchronous Iterable from a synchronous Iterable.

Next let’s look at how to create an asynchronous Iterable from an asynchronous Iterable.

We know from the previous section that you can use for-await-of to read asynchronous Iterable data, so we can use it like this:

async function* prefixLines(asyncIterable) {
    for await (const line of asyncIterable) {
        yield &#39;> &#39; + line;
    }
}

在generator一文中,我们讲到了在generator中调用generator。也就是在一个生产器中通过使用yield*来调用另外一个生成器。

同样的,如果是在异步生成器中,我们可以做同样的事情:

async function* gen1() {
    yield &#39;a&#39;;
    yield &#39;b&#39;;
    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(&#39;Problem!&#39;);
}
asyncGenerator().next()
.catch(err => console.log(err)); // Error: Problem!

异步方法和异步生成器


异步方法是使用async function 声明的方法,它会返回一个Promise对象。

function中的return或throw异常会作为返回的Promise中的value。

(async function () {
    return &#39;hello&#39;;
})()
.then(x => console.log(x)); // hello

(async function () {
    throw new Error(&#39;Problem!&#39;);
})()
.catch(x => console.error(x)); // Error: Problem!

异步生成器是使用 async function * 申明的方法。它会返回一个异步的iterable。

通过调用iterable的next方法,将会返回一个Promise。异步生成器中yield 的值会用来填充Promise的值。如果在生成器中抛出了异常,同样会被Promise捕获到。

async function* gen() {
    yield &#39;hello&#39;;
}
const genObj = gen();
genObj.next().then(x => console.log(x));
    // { value: &#39;hello&#39;, done: false }

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/es9-async-iteration/

更多编程相关知识,请访问:编程视频!!

The above is the detailed content of Detailed explanation of the new feature Async iteration in ES9. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:flydean的博客. If there is any infringement, please contact admin@php.cn delete