Discussion expliquée sur la programmation asynchrone en Javascript

Bonjour Ă  tous!

Comme vous vous en souvenez peut-ĂȘtre, en octobre, nous traduisions un article intĂ©ressant sur l'utilisation des minuteries en Javascript. Cela a provoquĂ© une Ă©norme discussion, selon les rĂ©sultats dont nous avons longtemps voulu revenir sur ce sujet et vous proposer une analyse dĂ©taillĂ©e de la programmation asynchrone dans ce langage. Nous sommes heureux d'avoir rĂ©ussi Ă  trouver du matĂ©riel dĂ©cent et Ă  le publier avant la fin de l'annĂ©e. Bonne lecture!

La programmation asynchrone en Javascript a traversĂ© une Ă©volution en plusieurs Ă©tapes: des rappels aux promesses et plus loin aux gĂ©nĂ©rateurs, et bientĂŽt Ă  l' async/await . À chaque Ă©tape, la programmation asynchrone en Javascript Ă©tait un peu simplifiĂ©e pour ceux qui s'Ă©taient dĂ©jĂ  mis Ă  genoux dans cette langue, mais pour les dĂ©butants elle devenait seulement plus effrayante, car il fallait comprendre les nuances de chaque paradigme, maĂźtriser l'application de chacun et, non moins important, comprendre, comment tout cela fonctionne.

Dans cet article, nous avons dĂ©cidĂ© de rappeler briĂšvement comment utiliser les rappels et les promesses, de donner une brĂšve introduction aux gĂ©nĂ©rateurs, puis de vous aider Ă  comprendre intuitivement exactement comment la programmation asynchrone «sous le capot» avec les gĂ©nĂ©rateurs et async / wait est organisĂ©e. Nous espĂ©rons que vous pourrez ainsi appliquer en toute confiance les diffĂ©rents paradigmes exactement lĂ  oĂč ils sont appropriĂ©s.

Il est supposé que le lecteur a déjà utilisé des rappels, des promesses et des générateurs pour la programmation asynchrone, et est également assez familier avec les fermetures et le curry en Javascript.

Enfer de rappel

Au dĂ©part, il y a eu des rappels. Javascript n'a pas d'E / S synchrones (ci-aprĂšs dĂ©nommĂ©es E / S) et le blocage n'est pas pris en charge du tout. Ainsi, pour organiser toute E / S ou diffĂ©rer une action, une telle stratĂ©gie a Ă©tĂ© choisie: le code qui devait ĂȘtre exĂ©cutĂ© de maniĂšre asynchrone a Ă©tĂ© transmis Ă  la fonction avec exĂ©cution diffĂ©rĂ©e, qui a Ă©tĂ© lancĂ©e quelque part en dessous dans la boucle d'Ă©vĂ©nement. Un rappel n'est pas si mauvais, mais le code se dĂ©veloppe et les rappels gĂ©nĂšrent gĂ©nĂ©ralement de nouveaux rappels. Le rĂ©sultat est quelque chose comme ceci:

 getUserData(function doStuff(e, a) { getMoreUserData(function doMoreStuff(e, b) { getEvenMoreUserData(function doEvenMoreStuff(e, c) { getYetMoreUserData(function doYetMoreStuff(e, c) { console.log('Welcome to callback hell!'); }); }); }); }) 

Mis Ă  part la chair de poule en voyant un tel code fractal, il y a un autre problĂšme: nous avons maintenant dĂ©lĂ©guĂ© le contrĂŽle de notre logique do*Stuff Ă  d'autres fonctions ( get*UserData() ), auxquelles vous n'avez peut-ĂȘtre pas de code source, et vous ne l'ĂȘtes peut-ĂȘtre pas sĂ»r qu'ils effectuent votre rappel. Super, non?

Promesses

Les promesses inversent l'inversion du contrĂŽle fourni par les rappels et aident Ă  dĂ©mĂȘler un enchevĂȘtrement de rappels dans une chaĂźne lisse.
Maintenant, l'exemple prĂ©cĂ©dent peut ĂȘtre converti en quelque chose comme ceci:

 getUserData() .then(getUserData) .then(doMoreStuff) .then(getEvenMoreUserData) .then(doEvenMoreStuff) .then(getYetMoreUserData) .then(doYetMoreStuff); 

DĂ©jĂ  pas si moche, hein?

Mais laisse moi !!! Regardons un exemple de rappel plus vital (mais encore largement artificiel):

 // ,     fetchJson(),   GET   , //    :         ,     –   // . function fetchJson(url, callback) { ... } fetchJson('/api/user/self', function(e, user) { fetchJson('/api/interests?userId=' + user.id, function(e, interests) { var recommendations = []; interests.forEach(function () { fetchJson('/api/recommendations?topic=' + interest, function(e, recommendation) { recommendations.push(recommendation); if (recommendations.length == interests.length) { render(profile, interests, recommendations); } }); }); }); }); 

Ainsi, nous sĂ©lectionnons le profil de l'utilisateur, puis ses intĂ©rĂȘts, puis, en fonction de ses intĂ©rĂȘts, nous sĂ©lectionnons des recommandations et, enfin, aprĂšs avoir rassemblĂ© toutes les recommandations, nous affichons la page. Un tel ensemble de rappels, dont, peut-ĂȘtre, peut ĂȘtre fier, mais, nĂ©anmoins, il est en quelque sorte hirsute. Rien, appliquez les promesses ici - et tout ira bien. Non?

fetchJson() notre méthode fetchJson() pour qu'elle renvoie une promesse au lieu d'accepter un rappel. Une promesse est résolue par un corps de réponse analysé au format JSON.

 fetchJson('/api/user/self') .then(function (user) { return fetchJson('/api/user/interests?userId=' + self.id); }) .then(function (interests) { return Promise.all[interests.map(i => fetchJson('/api/recommendations?topic=' + i))]; }) .then(function (recommendations) { render(user, interests, recommendations); }); 

Bien, non? Quel est le problĂšme avec ce code maintenant?

... Oups! ..
Nous n'avons pas accĂšs au profil ou aux intĂ©rĂȘts dans la derniĂšre fonction de cette chaĂźne? Donc rien ne marche! Que faire? Essayons les promesses imbriquĂ©es:

 fetchJson('/api/user/self') .then(function (user) { return fetchJson('/api/user/interests?userId=' + self.id) .then(interests => { user: user, interests: interests }); }) .then(function (blob) { return Promise.all[blob.interests.map(i => fetchJson('/api/recommendations?topic=' + i))] .then(recommendations => { user: blob.user, interests: blob.interests, recommendations: recommendations }); }) .then(function (bigBlob) { render(bigBlob.user, bigBlob.interests, bigBlob.recommendations); }); 

Oui ... maintenant ça a l'air beaucoup plus maladroit que nous l'espérions. Est-ce à cause de ces poupées gigognes si folles que nous avons surtout cherché à sortir de l'enfer des rappels? Que faire maintenant?

Le code peut ĂȘtre un peu peignĂ©, en s'appuyant sur les fermetures:

 //   ,     var user, interests; fetchJson('/api/user/self') .then(function (fetchedUser) { user = fetchedUser; return fetchJson('/api/user/interests?userId=' + self.id); }) .then(function (fetchedInterests) { interests = fetchedInterests; return Promise.all(interests.map(i => fetchJson('/api/recommendations?topic=' + i))); }) .then(function (recomendations) { render(user, interests, recommendations); }) .then(function () { console.log('We are done!'); }); 

Oui, maintenant tout est pratiquement comme nous le voulions, mais avec une bizarrerie. Remarquez comment nous avons appelĂ© les arguments Ă  l'intĂ©rieur des rappels dans les fetchedInterests fetchedUser et fetchedInterests , plutĂŽt que l' user et les interests ? Si oui, alors vous ĂȘtes trĂšs attentif!

La faille de cette approche est la suivante: vous devez ĂȘtre trĂšs, trĂšs prudent pour ne rien nommer dans les fonctions internes ainsi que les variables du cache que vous allez utiliser dans votre fermeture. MĂȘme si vous avez le don d'Ă©viter les ombres, rĂ©fĂ©rencer une variable si Ă©levĂ©e dans la fermeture semble toujours assez dangereux, et ce n'est certainement pas bon.

Générateurs asynchrones

Les gĂ©nĂ©rateurs vous aideront! Si vous utilisez des gĂ©nĂ©rateurs, alors toute l'excitation disparaĂźt. Juste magique. La vĂ©ritĂ© est. Jetez un Ɠil uniquement:

 co(function* () { var user = yield fetchJson('/api/user/self'); var interests = yield fetchJson('/api/user/interests?userId=' + self.id); var recommendations = yield Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); }); 

C’est tout. Ça va marcher. Vous ne fondez pas en larmes quand vous voyez Ă  quel point les gĂ©nĂ©rateurs sont beaux, regrettez-vous d'avoir Ă©tĂ© si myope et d'avoir commencĂ© Ă  apprendre Javascript avant que les gĂ©nĂ©rateurs n'y apparaissent? J'avoue, une telle idĂ©e m'a une fois rendu visite.
Mais ... comment ça marche? Vraiment magique?

Bien sûr! .. Non. Nous nous tournons vers l'exposition.

Générateurs

Dans notre exemple, il semble que les générateurs soient faciles à utiliser, mais en fait, il se passe beaucoup de choses en eux. Pour en savoir plus sur les générateurs asynchrones, vous devez mieux comprendre comment fonctionnent les générateurs et comment ils fournissent une exécution asynchrone, qui semble synchrone.

Comme son nom l'indique, le générateur fait les valeurs:

 function* counts(start) { yield start + 1; yield start + 2; yield start + 3; return start + 4; } const counter = counts(0); console.log(counter.next()); // {value: 1, done: false} console.log(counter.next()); // {value: 2, done: false} console.log(counter.next()); // {value: 3, done: false} console.log(counter.next()); // {value: 4, done: true} console.log(counter.next()); // {value: undefined, done: true} 

C'est assez simple, mais de toute façon, parlons de ce qui se passe ici:

  1. const counter = counts(); - initialiser le générateur et l'enregistrer dans le compteur de variables. Le générateur est dans les limbes; aucun code dans le corps du générateur n'a encore été exécuté.
  2. console.log(counter.next()); - InterprĂ©tation de la sortie ( yield ) 1, aprĂšs quoi 1 est retournĂ© en tant que value et done aboutit Ă  false , car la sortie ne s'arrĂȘte pas lĂ 
  3. console.log(counter.next()); - Maintenant 2!
  4. console.log(counter.next()); - Maintenant 3! Terminé. Tout va bien? Non. L'exécution est interrompue au niveau du yield 3; Pour terminer, vous devez appeler à nouveau next ().
  5. console.log(counter.next()); - Maintenant 4, et il revient, mais pas Ă©mis, alors maintenant nous quittons la fonction, et tout est prĂȘt.
  6. console.log(counter.next()); - Le générateur a fini de fonctionner! Il n'a rien à signaler, sauf que "tout est fait".

Nous avons donc compris comment fonctionnent les générateurs! Mais attendez, quelle vérité choquante: les générateurs peuvent non seulement vomir des valeurs, mais aussi les dévorer!

 function* printer() { console.log("We are starting!"); console.log(yield); console.log(yield); console.log(yield); console.log("We are done!"); } const counter = printer(); counter.next(1); // ! counter.next(2); // 2 counter.next(3); // 3 counter.next(4); // 4\n ! counter.next(5); //    

Ouf, quoi?! Un générateur consomme des valeurs, au lieu de les engendrer. Comment est-ce possible?

Le secret est dans la fonction next . Il renvoie non seulement les valeurs du gĂ©nĂ©rateur, mais peut Ă©galement les renvoyer au gĂ©nĂ©rateur. Si vous dites next() argument, alors l'opĂ©ration yield , que le gĂ©nĂ©rateur attend actuellement, se traduit en fait par l'argument. C'est pourquoi le premier counter.next(1) enregistrĂ© comme undefined . Il n'y a tout simplement aucune extradition qui pourrait ĂȘtre rĂ©solue.

C'est comme si le gĂ©nĂ©rateur permettait au code appelant (procĂ©dure) et au code gĂ©nĂ©rateur (procĂ©dure) de s'associer afin qu'ils se transmettent des valeurs au fur et Ă  mesure de leur exĂ©cution et s'attendent. La situation est pratiquement la mĂȘme, comme si pour les gĂ©nĂ©rateurs Javascript la possibilitĂ© de mettre en Ɠuvre des procĂ©dures coopĂ©ratives exĂ©cutĂ©es de maniĂšre compĂ©titive, ce sont aussi des «coroutines», aurait Ă©tĂ© envisagĂ©e. En fait, Ă  peu prĂšs comme co() , non?

Mais ne nous prĂ©cipitons pas, sinon nous nous surpasserons. Dans ce cas, il est important que le lecteur saisisse intuitivement l'essence des gĂ©nĂ©rateurs et de la programmation asynchrone, et la meilleure façon de le faire est d'assembler le gĂ©nĂ©rateur vous-mĂȘme. N'Ă©crivez pas de fonction de gĂ©nĂ©rateur et n'utilisez pas la fonction terminĂ©e, mais recrĂ©ez vous-mĂȘme l'intĂ©rieur de la fonction de gĂ©nĂ©rateur.

Le dispositif interne du générateur - nous générons des générateurs

D'accord, je ne sais vraiment pas Ă  quoi ressemblent exactement les composants internes du gĂ©nĂ©rateur dans les diffĂ©rents runtimes JS. Mais ce n'est pas si important. Les gĂ©nĂ©rateurs correspondent Ă  l'interface. Un «constructeur» pour instancier un gĂ©nĂ©rateur, la mĂ©thode next(value? : any) , avec laquelle nous ordonnons au gĂ©nĂ©rateur de continuer Ă  travailler et lui donner des valeurs, une autre mĂ©thode throw(error) au cas oĂč une throw(error) gĂ©nĂ©rĂ©e Ă  la place d'une valeur, et enfin une mĂ©thode return() , qui est toujours silencieux. Si la conformitĂ© avec l'interface est atteinte, alors tout va bien.

Essayons donc de construire le générateur count counts() susmentionné sur ES5 pur, sans le mot clé function* . Pour l'instant, vous pouvez ignorer throw() et passer la valeur à next() , car la méthode n'accepte aucune entrée. Comment faire

Mais en Javascript, il existe un autre mécanisme pour suspendre et reprendre l'exécution du programme: les fermetures! Cela vous semble familier?

 function makeCounter() { var count = 1; return function () { return count++; } } var counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3 

Si vous avez déjà utilisé des fermetures, je suis sûr que vous avez déjà écrit quelque chose comme ça. La fonction retournée par makeCounter peut générer une séquence infinie de nombres, tout comme un générateur.

Cependant, cette fonction ne correspond pas Ă  l'interface du gĂ©nĂ©rateur, et elle ne peut pas ĂȘtre directement appliquĂ©e dans notre exemple avec counts() , qui renvoie 4 valeurs et quitte. Que faut-il pour une approche universelle de l'Ă©criture de fonctions de type gĂ©nĂ©rateur?

Fermetures, machines d'état et travaux forcés!

 function counts(start) { let state = 0; let done = false; function go() { let result; switch (state) { case 0: result = start + 1; state = 1; break; case 1: result = start + 2; state = 2; break; case 2: result = start + 3; state = 3; break; case 3: result = start + 4; done = true; state = -1; break; default: break; } return {done: done, value: result}; } return { next: go } } const counter = counts(0); console.log(counter.next()); // {value: 1, done: false} console.log(counter.next()); // {value: 2, done: false} console.log(counter.next()); // {value: 3, done: false} console.log(counter.next()); // {value: 4, done: true} console.log(counter.next()); // {value: undefined, done: true} 

En exĂ©cutant ce code, vous verrez les mĂȘmes rĂ©sultats que dans la version avec le gĂ©nĂ©rateur. Bien, non?
Donc, nous avons trié le cÎté générateur du générateur; analysons la consommation?
En fait, il n'y a pas beaucoup de différences.

 function printer(start) { let state = 0; let done = false; function go(input) { let result; switch (state) { case 0: console.log("We are starting!"); state = 1; break; case 1: console.log(input); state = 2; break; case 2: console.log(input); state = 3; break; case 3: console.log(input); console.log("We are done!"); done = true; state = -1; break; default: break; return {done: done, value: result}; } } return { next: go } } const counter = printer(); counter.next(1); // ! counter.next(2); // 2 counter.next(3); // 3 counter.next(4); // 4 counter.next(5); // ! 

Tout ce qui est nécessaire est d'ajouter une input comme argument go , et les valeurs sont redirigées. Ressemble à la magie? Presque comme des générateurs?

Hourra! Nous avons donc recréé le générateur en tant que fournisseur et en tant que consommateur. Pourquoi ne pas essayer d'y combiner ces fonctions? Voici un autre exemple assez artificiel d'un générateur:

 function* adder(initialValue) { let sum = initialValue; while (true) { sum += yield sum; } } 

Étant donnĂ© que nous sommes tous spĂ©cialistes des gĂ©nĂ©rateurs, nous comprenons que ce gĂ©nĂ©rateur ajoute la valeur donnĂ©e dans next(value) Ă  sum , puis renvoie sum. Cela fonctionne exactement comme prĂ©vu:

 const add = adder(0); console.log(add.next()); // 0 console.log(add.next(1)); // 1 console.log(add.next(2)); // 3 console.log(add.next(3)); // 6 

Cool. Écrivons maintenant cette interface comme une fonction normale!

 function adder(initialValue) { let state = 'initial'; let done = false; let sum = initialValue; function go(input) { let result; switch (state) { case 'initial': result = initialValue; state = 'loop'; break; case 'loop': sum += input; result = sum; state = 'loop'; break; default: break; } return {done: done, value: result}; } return { next: go } } function runner() { const add = adder(0); console.log(add.next()); // 0 console.log(add.next(1)); // 1 console.log(add.next(2)); // 3 console.log(add.next(3)); // 6 } runner(); 

Wow, nous avons mis en place une coroutine Ă  part entiĂšre.

Il y a encore quelque chose à discuter sur le fonctionnement des générateurs. Comment fonctionnent les exceptions? Avec les exceptions qui se produisent à l'intérieur des générateurs, tout est simple: next() fera que l'exception atteindra l'appelant et le générateur mourra. Passer une exception au générateur se fait dans la méthode throw() , que nous avons omise ci-dessus.

Enrichissons notre terminateur avec une nouvelle fonctionnalité intéressante. Si l'appelant transmet l'exception au générateur, il reviendra à la derniÚre valeur de la somme.

 function* adder(initialValue) { let sum = initialValue; let lastSum = initialValue; let temp; while (true) { try { temp = sum; sum += yield sum; lastSum = temp; } catch (e) { sum = lastSum; } } } const add = adder(0); console.log(add.next()); // 0 console.log(add.next(1)); // 1 console.log(add.next(2)); // 3 console.log(add.throw(new Error('BOO)!'))); // 1 console.log(add.next(4)); // 5 

ProblÚme de programmation - Pénétration d'erreur de générateur

Camarade, comment implémenter throw ()?

C'est facile! L'erreur n'est qu'une autre valeur. Nous pouvons le passer à go() comme argument suivant. En fait, une certaine prudence s'impose ici. Lorsque throw(e) appelé, l' yield fonctionnera comme si nous avions écrit throw e. Cela signifie que nous devons vérifier les erreurs à chaque état de notre machine d'état et planter le programme si nous ne pouvons pas gérer l'erreur.

Commençons par l'implémentation précédente du terminateur, copiée

Motif

Solution

Boum! Nous avons implémenté un ensemble de coroutines capables de se transmettre des messages et des exceptions, tout comme un vrai générateur.

Mais la situation empire, non? L'implĂ©mentation de la machine d'Ă©tat s'Ă©loigne de plus en plus de l'implĂ©mentation du gĂ©nĂ©rateur. Non seulement cela, en raison de la gestion des erreurs, le code est encombrĂ© de dĂ©chets; le code est d'autant plus compliquĂ© en raison de la longue while que nous avons ici. Pour convertir une while vous devez la «dĂ©mĂȘler» en Ă©tats. Donc, notre cas 1 comprend en fait 2,5 itĂ©rations de la while , car le yield casse au milieu. Enfin, vous devez ajouter du code supplĂ©mentaire pour pousser les exceptions de l'appelant et vice versa s'il n'y a pas de try/catch dans le gĂ©nĂ©rateur pour gĂ©rer cette exception.

Tu l'as fait !!! Nous avons terminĂ© une analyse dĂ©taillĂ©e des alternatives possibles pour la mise en Ɠuvre des gĂ©nĂ©rateurs et, j'espĂšre, vous avez dĂ©jĂ  mieux compris le fonctionnement des gĂ©nĂ©rateurs. Dans le rĂ©sidu sec:

  • Un gĂ©nĂ©rateur peut gĂ©nĂ©rer des valeurs, consommer des valeurs ou les deux.
  • L'Ă©tat du gĂ©nĂ©rateur peut ĂȘtre suspendu (Ă©tat, machine d'Ă©tat, prise?)
  • L'appelant et le gĂ©nĂ©rateur vous permettent de former un ensemble de corutine, en interaction les uns avec les autres
  • Les exceptions sont transmises dans n'importe quelle direction.

Maintenant que nous avons une meilleure comprĂ©hension des gĂ©nĂ©rateurs, je propose un moyen potentiellement pratique de les raisonner: ce sont des constructions syntaxiques avec lesquelles vous pouvez Ă©crire des procĂ©dures exĂ©cutĂ©es de maniĂšre compĂ©titive qui se transmettent des valeurs les unes aux autres via un canal qui transmet les valeurs une par une ( yield ). Cela sera utile dans la section suivante, oĂč nous produirons une implĂ©mentation de co() partir de coroutine.

Inversion du contrĂŽle de la corutine

Maintenant que nous sommes qualifiĂ©s pour travailler avec des gĂ©nĂ©rateurs, rĂ©flĂ©chissons Ă  la façon dont ils peuvent ĂȘtre utilisĂ©s dans la programmation asynchrone. Si nous pouvons Ă©crire des gĂ©nĂ©rateurs en tant que tels, cela ne signifie pas que les promesses dans les gĂ©nĂ©rateurs seront automatiquement rĂ©solues. Mais attendez, les gĂ©nĂ©rateurs ne sont pas censĂ©s fonctionner seuls. Ils doivent interagir avec un autre programme, la procĂ©dure principale, celle qui appelle .next() et .throw() .

Et si nous plaçons notre logique métier non pas dans la procédure principale, mais dans le générateur? Chaque fois qu'une certaine valeur asynchrone se produit, comme une promesse, pour la logique métier, le générateur dira: "Je ne veux pas jouer avec ce non-sens, me réveiller quand il se résout", mettra en pause et émettra une promesse à la procédure de service. Procédure de maintenance: "OK, je vous rappelle plus tard." AprÚs quoi, il enregistre un rappel avec cette promesse, quitte et attend jusqu'à ce qu'il soit possible de déclencher un cycle d'événements (c'est-à-dire lorsque la promesse se résout). Lorsque cela se produit, la procédure annoncera «hé, c'est votre tour» et enverra la valeur via .next() générateur de sommeil. Elle attendra que le générateur fasse son travail, et en attendant, il fera d'autres choses asynchrones ... et ainsi de suite. Vous avez écouté une triste histoire sur la façon dont la procédure se poursuit au service d'un générateur.

Revenons donc au sujet principal. Maintenant que nous savons comment fonctionnent les gĂ©nĂ©rateurs et les promesses, il ne nous sera pas difficile de crĂ©er une telle «procĂ©dure de service». La procĂ©dure de service elle-mĂȘme sera exĂ©cutĂ©e de maniĂšre compĂ©titive comme une promesse, instanciera et maintiendra le gĂ©nĂ©rateur, puis reviendra au rĂ©sultat final de notre procĂ©dure principale en utilisant le rappel .then() .

Ensuite, revenons au programme co () et discutons-en plus en détail. co() est une procédure de service qui prend du travail esclave afin que le générateur ne puisse fonctionner qu'avec des valeurs synchrones. Semble déjà beaucoup plus logique, non?

 co(function* () { var user = yield fetchJson('/api/user/self'); var interests = yield fetchJson('/api/user/interests?userId=' + self.id); var recommendations = yield Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); }); 

, , co() , .

— co()

Super! co() , , . co()

  1. ,
  2. .next() , {done: false, value: [a Promise]}
  3. ( ), .next() ,
  4. , 4
  5. - {done: true, value: ...} , , co()

, co(), :



 function deferred(val) { return new Promise((resolve, reject) => resolve(val)); } co(function* asyncAdds(initialValue) { console.log(yield deferred(initialValue + 1)); console.log(yield deferred(initialValue + 2)); console.log(yield deferred(initialValue + 3)); }); function co(generator) { return new Promise((resolve, reject) => { //   }); } 



, ? - 10 co() , . , . ?

– co()

, , , , co() . , .throw() .



 function deferred(val) { return new Promise((resolve, reject) => resolve(val)); } function deferReject(e) { return new Promise((resolve, reject) => reject(e)); } co(function* asyncAdds() { console.log(yield deferred(1)); try { console.log(yield deferredError(new Error('To fail, or to not fail.'))); } catch (e) { console.log('To not fail!'); } console.log(yield deferred(3)); }); function co(generator) { return new Promise((resolve, reject) => { //   }); } 



. , , .next() onResolve() . onReject() , .throw() . try/catch , , try/catch .

, co() ! ! co() , , , . , ?

: async/await

co() . - , async/await? — ! , async await .

async , await , yield . await , async . async - .

, async/await , , - co() async yield await , * , .

 co(function* () { var user = yield fetchJson('/api/user/self'); var interests = yield fetchJson('/api/user/interests?userId=' + self.id); var recommendations = yield Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); }); 

:

 async function () { var user = await fetchJson('/api/user/self'); var interests = await fetchJson('/api/user/interests?userId=' + self.id); var recommendations = await Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); }(); 

, :

  • co() . async , . async co() co.wrap() .
  • co() ( yield ) , , . async ( await ) .



Javascript , , « » co() , , , async/await . ? .

Source: https://habr.com/ru/post/fr434360/


All Articles