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
3 Réponses :
Vous devriez plutôt faire de Il semble que la fonction Si vous n'avez pas fait 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))
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))
test
un générateur, test
devrait renvoyer un iterator (un objet avec une propriété value
et un next code> fonction).
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 . :-)
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 }); } }; }
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.
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:
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))
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.)
Vous devez utiliser des générateurs et un rendement au lieu d'un retour. devrait fonctionner alors