Maison  >  Article  >  interface Web  >  Explication détaillée de la nouvelle fonctionnalité Itération asynchrone dans ES9

Explication détaillée de la nouvelle fonctionnalité Itération asynchrone dans ES9

青灯夜游
青灯夜游avant
2021-04-20 09:49:062441parcourir

Explication détaillée de la nouvelle fonctionnalité Itération asynchrone dans ES9

Dans ES6, le concept d'itération synchrone a été introduit avec la référence de l'opérateur Async dans ES8, peut-il être parcouru dans une opération asynchrone ?

Aujourd'hui, je vais vous parler de la nouvelle fonctionnalité de parcours asynchrone dans ES9, l'itération Async.

Parcours asynchrone


Avant d'expliquer le parcours asynchrone, rappelons d'abord le parcours synchrone dans ES6.

Selon la définition d'ES6, l'itération se compose principalement de trois parties :

1 Itérable

Regardons d'abord la définition. of Iterable :

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

Iterable signifie qu'il y a des données traversables dans cet objet et qu'une méthode d'usine qui peut générer un itérateur doit être implémentée.

2. Iterator

interface Iterator {
    next() : IteratorResult;
}

Iterator peut être construit à partir d'Iterable. Iterator est un concept semblable à un curseur, et IteratorResult est accessible via next.

3. IteratorResult

IteratorResult sont les données obtenues à chaque appel de la méthode suivante.

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

En plus d'une valeur indiquant les données à obtenir, IteratorResult a également un done indiquant si le parcours est terminé.

Ce qui suit est un exemple de parcours d'un tableau :

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

Mais l'exemple ci-dessus traverse des données synchrones, si nous obtenons des données asynchrones, comme un fichier téléchargé depuis l'extrémité http, nous voulons. Il faut parcourir le fichier ligne par ligne. La lecture d’une ligne de données étant une opération asynchrone, cela implique un parcours de données asynchrone.

La méthode pour ajouter une lecture asynchrone des fichiers est readLinesFromFile, donc la méthode de traversée synchrone n'est plus applicable à l'asynchrone :

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

Peut-être penserez-vous, pouvons-nous utiliser l'asynchrone. Que diriez-vous d'encapsuler l'opération de lire une ligne dans une promesse puis de la parcourir de manière synchrone ?

L'idée est bonne, mais dans ce cas, il est impossible de détecter si l'opération asynchrone est terminée. La méthode n’est donc pas réalisable.

ES9 a donc introduit le concept de parcours asynchrone :

  • L'itérateur dans les itérables asynchrones peut être obtenu via Symbol.asyncIterator.

  • La méthode next() de l'itérateur asynchrone renvoie un objet Promises, qui contient IteratorResults.

Alors, regardons la définition API du parcours asynchrone :

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

Regardons une application de parcours asynchrone :

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

où createAsyncIterable sera A L'itérable synchrone est converti en un itérable asynchrone. Nous verrons comment il est généré dans la section suivante.

Ici, nous nous concentrons principalement sur l'opération de traversée d'asyncIterator.

Étant donné que l'opérateur Async a été introduit dans ES8, nous pouvons également réécrire le code ci-dessus à l'aide de la fonction Async :

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

Parcours itérable asynchrone


Utilisez for-of pour parcourir un itérable synchrone et utilisez for-wait-of pour parcourir un itérable asynchrone.

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

Notez que wait doit être placé dans la fonction async.

Si une exception se produit pendant notre parcours asynchrone, vous pouvez utiliser try catch in for-await-of pour intercepter l'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!
    }
})();

L'itérable synchronisé renvoie les itérateurs synchrones, next La méthode renvoie {value , fait}.

Si vous utilisez for-await-of, les itérateurs synchrones seront convertis en itérateurs asynchrones. La valeur renvoyée est ensuite convertie en promesse.

Si la valeur renvoyée par synchronous next lui-même est un objet Promise, la valeur de retour asynchrone est toujours la même promesse.

C'est-à-dire qu'il convertira : Iterableea14b2e011575f0c7465d02dc55e095c> en AsyncIterable8742468051c85b06f0a0af9e3e506b5c, comme le montre l'exemple suivant :

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

L'exemple ci-dessus convertit une promesse synchrone en une promesse asynchrone.

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

// Output:
// c
// d

L'exemple ci-dessus convertit les constantes synchronisées en Promise. On voit que les résultats des deux sont les mêmes.

Génération d'itérable asynchrone


Retour à l'exemple ci-dessus, nous utilisons createAsyncIterable(syncIterable) pour convertir syncIterable en AsyncIterable.

Voyons comment cette méthode est implémentée :

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

Dans le code ci-dessus, nous ajoutons async devant une fonction de générateur ordinaire, qui représente un générateur asynchrone.

Pour les générateurs ordinaires, chaque fois que la méthode suivante est appelée, un objet {value,done} sera renvoyé Cet objet objet est une encapsulation de la valeur de rendement.

Pour un générateur asynchrone, chaque fois que la méthode suivante est appelée, un objet de promesse contenant l'objet {value,done} sera renvoyé. Cet objet objet est une encapsulation de la valeur de rendement.

Étant donné qu'un objet Promise est renvoyé, nous n'avons pas besoin d'attendre la fin du résultat de l'exécution asynchrone avant d'appeler à nouveau la méthode suivante.

Nous pouvons utiliser un Promise.all pour effectuer toutes les opérations Promise asynchrones en même temps :

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

Dans createAsyncIterable, nous créons un Iterable asynchrone à partir d'un Iterable synchrone.

Voyons ensuite comment créer un Iterable asynchrone à partir d'un Iterable asynchrone.

De la section précédente, nous savons que vous pouvez utiliser for-await-of pour lire des données Itérables asynchrones, nous pouvons donc l'utiliser comme ceci :

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/

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer