4
votes

Utilisation de Symbol.asyncIterator de javascript avec for await of loop

J'essaie de comprendre le symbole de javascript .asyncIterator et pour attendre de . J'ai écrit un code simple et cela génère une erreur disant:

let a = {}


function test() {
        for(let i=0; i < 10; i++) {
                if(i > 5) {
                        return Promise.resolve(`Greater than 5: (${i})`)
                }else {
                        return Promise.resolve(`Less than 5: (${i})`)
                }
        }
}

a[Symbol.asyncIterator] = test;


async function main() {
        for await (let x of a) { // LINE THAT THROWS AN ERROR
                console.log(x)
        }
}


main()
        .then(r => console.log(r))
        .catch(err => console.log(err))

sur la ligne qui essaie d'utiliser pour await (let x of a) .

Je ne pouvais pas en comprendre la raison.

    TypeError: undefined is not a function

Je crée un objet vide a et insère une clé Symbol.asyncIterator sur le même objet et lui attribue un fonction nommée test qui renvoie une Promise . Ensuite, j'utilise la boucle pour await of pour parcourir toutes les valeurs que la fonction renverrait.

Qu'est-ce que je fais de manière incorrecte?

PS: Je suis sur la version Node 10.13.0 et sur la dernière version de Chrome


1 commentaires

Vous devez utiliser des générateurs et un rendement au lieu d'un retour. devrait fonctionner alors


3 Réponses :


0
votes

Vous devriez plutôt faire de test une async fonction de générateur et yield au lieu de return :

const delay = ms => new Promise(res => setTimeout(res, ms));
let a = {}


async function* test() {
  for(let i=0; i < 10; i++) {
    await delay(500);
    if(i > 5) {
      yield `Greater than 5: (${i})`;
    }else {
      yield `Less than 5: (${i})`;
    }
  }
}

a[Symbol.asyncIterator] = test;


async function main() {
  for await (let x of a) {
    console.log(x)
  }
}


main()
  .then(r => console.log(r))
  .catch(err => console.log(err))

Il semble que la fonction test doit être asynchrone pour que le x dans le for wait soit déballé, même bien que test n'attende nulle part, sinon le x sera une promesse qui résout la valeur, pas la valeur elle-même. p >

yield ing Promise.resolve dans un générateur asynchrone est cependant étrange - sauf si vous voulez que le résultat soit une promesse (qui nécessiterait un wait supplémentaire dans la boucle for await ), il sera plus logique de wait dans le code async > générateur, puis donnent le résultat.

let a = {}


async function* test() {
  for(let i=0; i < 10; i++) {
    if(i > 5) {
      yield Promise.resolve(`Greater than 5: (${i})`)
    }else {
      yield Promise.resolve(`Less than 5: (${i})`)
    }
  }
}

a[Symbol.asyncIterator] = test;


async function main() {
  for await (let x of a) {
    console.log(x)
  }
}


main()
  .then(r => console.log(r))
  .catch(err => console.log(err))

Si vous n'avez pas fait de test un générateur, test devrait renvoyer un iterator (un objet avec une propriété value et un next code> fonction).


6 commentaires

Ce n'est cependant pas un itérateur asynchrone.


Vous avez écrit un générateur asynchrone. Il n'y a aucune raison d'utiliser Promise.resolve si vous allez utiliser un générateur asynchrone pour ce faire, donnez directement les valeurs; il les enveloppe pour vous. Mais je pensais que le point du PO était qu'ils voulaient comprendre cela sous les couvertures.


@TJCrowder Je pensais que le Promise.resolve était là juste pour imiter quelque chose d'asynchrone en cours - dans un code non-exemple, il y aurait probablement un wait là-dedans, donc la fonction async aurait plus de sens, non?


@CertainPerformance - Il n'y a aucune raison de rendre wait somePromise dans un générateur asynchrone, tout comme il n'y a aucune raison de return wait somePromise dans une fonction async ; dans les deux cas, la valeur est automatiquement attendue (effectivement). Jusqu'à un récent changement de spécification, l'ajout de await ne faisait que retarder les choses par une coche asynchrone supplémentaire. (Avec le dernier changement de spécification, effectué juste après ES2019, tant que ce que vous attendez est une promesse native , cette coche supplémentaire est optimisée.)


@tjcrowder pourtant la proposition utilise yield wait lui-même.


@JonasWilms - Eh bien, personne n'est parfait. :-) Il ne serait pas surprenant que le texte de la proposition devienne légèrement obsolète au fur et à mesure que les choses avancent. Pour plus de détails sur les modifications visant à améliorer les performances de yield await promise et return await promise : v8.dev/blog/fast-async Voir également les réponses de Mathias Bynens à mon tweet ici . :-)



2
votes

La fonction test ne doit pas renvoyer une promesse, mais un Iterator (un objet avec une méthode next () ), cette méthode doit alors renvoyer une Promise (qui en fait un itérateur asynchrone) et que Promise doit se résoudre en un objet contenant une valeur et une clé done :

async function* test() {
  for(let i = 0; i < 10; i++) {
    if(i > 5) {
      await Promise.resolve();
      yield `Greater than 5: (${i})`;
    }else {
      await Promise.resolve();
      yield `Less than 5: (${i})`;
    }
  }
}

Maintenant pendant cela fonctionne, ce n'est pas encore si utile. Vous pouvez cependant créer le même comportement avec une fonction de générateur asynchrone:

  async function* test() {
    await Promise.resolve();
    yield "test";
  }

Ou dans votre cas:

function test() {
   return {
     next() {
       return Promise.resolve({ value: "test", done: false });
     }
   };
}


3 commentaires

Pourriez-vous expliquer le but de await Promise.resolve ();


@suhail await arrête l'itérateur jusqu'à ce que Promise.resolve () ou toute autre action asynchrone encapsulée dans une promesse, soit effectuée.


@SuhailGupta - Par exemple, dans ce qui précède, cela montre simplement l'utilisation de la puissance de async / await dans le générateur async.



6
votes

Pour être un asyncIterator valide, votre fonction test doit renvoyer un objet avec une méthode next qui renvoie une promesse d'objet résultat avec Propriétés value et done . (Techniquement, la valeur est facultative si sa valeur serait non définie et done est facultative si sa valeur serait false , mais ...)

Vous pouvez le faire de plusieurs manières:

  1. Complètement manuellement (gênant, surtout si vous voulez le bon prototype)
  2. Demi-manuel (un peu moins gênant, mais toujours gênant pour obtenir le bon prototype)
  3. Utilisation d'une fonction de générateur asynchrone (la plus simple)

Vous pouvez le faire complètement manuellement (cela n'essaie pas d'obtenir le bon prototype):

return Object.assign(Object.create(asyncIteratorPrototype), {
    next() {
        // ...
    }
});

Vous pouvez le faire en écrivant à moitié manuellement une fonction qui renvoie un objet avec une méthode async next (n'essaye toujours pas d'obtenir le bon prototype):

const asyncIteratorPrototype =
    Object.getPrototypeOf(
        Object.getPrototypeOf(
            async function*(){}.prototype
        )
    );

Ou vous pouvez simplement utiliser une fonction de générateur async (la plus simple, et obtient automatiquement le bon prototype):

async function* test() {
    for (let i = 0; i < 10; ++i) {
        yield i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`;
    }
}

let a = {
    [Symbol.asyncIterator]: test
};

async function main() {
    for await (let x of a) {
        console.log(x)
    }
}

main()
    .then(r => console.log(r))
    .catch(err => console.log(err))

À propos des prototypes: tous les itérateurs asynchrones que vous obtenez du moteur d'exécution JavaScript lui-même héritent d'un prototype qui fournit la fonction très basique de garantir que l'itérateur est également itérable (en ayant Symbol.iterator soit une fonction renvoyant this ). Il n'y a pas d'identificateur ou de propriété publiquement disponible pour ce prototype, vous devez sauter à travers des obstacles pour l'obtenir:

function test() {
    let i = -1;
    return {
        async next() {
            ++i;
            if (i >= 10) {
                return {
                    value: undefined,
                    done: true
                };
            }
            return {
                value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
                done: false
            };
        }
    };
}

let a = {
    [Symbol.asyncIterator]: test
};

async function main() {
    for await (let x of a) {
        console.log(x)
    }
}

main()
    .then(r => console.log(r))
    .catch(err => console.log(err))

Ensuite, vous l'utiliseriez comme prototype de l'objet avec la méthode next que vous renvoyez:

function test() {
    let i = -1;
    return {
        next() {
            ++i;
            if (i >= 10) {
                return Promise.resolve({
                    value: undefined,
                    done: true
                });
            }
            return Promise.resolve({
                value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
                done: false
            });
        }
    };
}

let a = {
    [Symbol.asyncIterator]: test
};

async function main() {
    for await (let x of a) {
        console.log(x)
    }
}

main()
    .then(r => console.log(r))
    .catch(err => console.log(err))


4 commentaires

Pour la dernière partie ... [Symbol.asyncIterator] () {ne retournerait pas ceci; } suffit-il?


@JonasWilms - Oui ... sauf si votre projet améliore les itérateurs asynchrones en ajoutant des fonctionnalités au prototype. :-) Puisque TC39 a rendu la tâche difficile pour obtenir le prototype, je ne pense pas que quiconque s'attend à ce que l'amélioration du prototype fonctionne avec autre chose que des itérateurs asynchrones natifs et ceux qu'ils ont eux-mêmes écrits pour assurer le prototype. Donc en pratique, presque tout le temps, oui.


Lors de l'utilisation avec yield , renvoie-t-il implicitement valeur et done ?


@SuhailGupta - Oui, le générateur / générateur asynchrone lui-même fournit l'objet de résultat wrapper, ce qui explique en partie pourquoi les générateurs sont si pratiques pour créer des itérateurs. Avec yield , l'objet résultat aura la valeur renvoyée et done: false . Avec return (dans une fonction de générateur), l'objet résultat aura la valeur renvoyée et done: true . (De même: for-await-of et for-of consomment l'objet de résultat, en utilisant sa valeur et next sous les couvertures.)