Erklärtes Gespräch über asynchrone Programmierung in Javascript

Hallo an alle!

Wie Sie sich vielleicht erinnern, haben wir bereits im Oktober einen interessanten Artikel über die Verwendung von Timern in Javascript übersetzt. Es hat eine große Diskussion ausgelöst, nach deren Ergebnissen wir schon lange auf dieses Thema zurückkommen und Ihnen eine detaillierte Analyse der asynchronen Programmierung in dieser Sprache anbieten möchten. Wir sind froh, dass wir es geschafft haben, anständiges Material zu finden und es vor Jahresende zu veröffentlichen. Viel Spaß beim Lesen!

Die asynchrone Programmierung in Javascript hat eine mehrstufige Entwicklung durchlaufen: von Rückrufen zu Versprechungen und weiter zu Generatoren und bald zu async/await . In jeder Phase wurde die asynchrone Programmierung in Javascript für diejenigen, die sich bereits in dieser Sprache niedergekniet hatten, ein wenig vereinfacht, aber für Anfänger wurde es nur erschreckender, da es notwendig war, die Nuancen jedes Paradigmas zu verstehen, die Anwendung jedes einzelnen zu beherrschen und, nicht weniger wichtig, zu verstehen, wie das alles funktioniert.

In diesem Artikel haben wir uns entschlossen, kurz daran zu erinnern, wie Rückrufe und Versprechen verwendet werden, eine kurze Einführung in Generatoren zu geben und Ihnen dann zu helfen, intuitiv genau zu verstehen, wie die asynchrone Programmierung „unter der Haube“ mit Generatoren und Async / Warten angeordnet ist. Wir hoffen, dass Sie auf diese Weise die verschiedenen Paradigmen genau dort sicher anwenden können, wo sie angemessen sind.

Es wird davon ausgegangen, dass der Leser bereits Rückrufe, Versprechen und Generatoren für die asynchrone Programmierung verwendet hat und auch mit Schließungen und Currying in Javascript vertraut ist.

Rückruf Hölle

Anfangs gab es Rückrufe. Javascript hat keine synchronen E / A (im Folgenden als E / A bezeichnet) und das Blockieren wird überhaupt nicht unterstützt. Um eine E / A zu organisieren oder eine Aktion zu verschieben, wurde eine solche Strategie gewählt: Der Code, der asynchron ausgeführt werden musste, wurde mit verzögerter Ausführung an die Funktion übergeben, die irgendwo unten in der Ereignisschleife gestartet wurde. Ein Rückruf ist nicht so schlecht, aber der Code wächst und Rückrufe führen normalerweise zu neuen Rückrufen. Das Ergebnis ist ungefähr so:

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

Abgesehen von den Gänsehautproblemen beim Anzeigen eines solchen Fraktalcodes gibt es noch ein weiteres Problem: Jetzt haben wir die Steuerung unserer do*Stuff Logik an andere Funktionen delegiert ( get*UserData() ), für die Sie möglicherweise keinen Quellcode haben und möglicherweise auch nicht sicher, ob sie Ihren Rückruf durchführen. Großartig, nicht wahr?

Versprechen

Versprechen kehren die Umkehrung der Kontrolle durch Rückrufe um und helfen dabei, ein Gewirr von Rückrufen in einer glatten Kette zu lösen.
Jetzt kann das vorherige Beispiel in etwa Folgendes konvertiert werden:

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

Schon nicht so hässlich, oder?

Aber lass mich !!! Schauen wir uns ein wichtigeres (aber immer noch weitgehend erfundenes) Rückrufbeispiel an:

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

Wir wählen also das Profil des Benutzers und dann seine Interessen aus. Anschließend wählen wir basierend auf seinen Interessen Empfehlungen aus. Nachdem wir alle Empfehlungen gesammelt haben, zeigen wir die Seite an. Solche Rückrufe, auf die man wahrscheinlich stolz sein kann, die aber irgendwie zottelig sind. Nichts, Versprechen hier anwenden - und alles wird klappen. Richtig?

Lassen Sie uns unsere fetchJson() -Methode so ändern, dass sie ein Versprechen zurückgibt, anstatt einen Rückruf zu akzeptieren. Ein Versprechen wird durch einen im JSON-Format analysierten Antworttext gelöst.

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

Schön, oder? Was ist jetzt mit diesem Code falsch?

... Ups! ..
Wir haben keinen Zugriff auf das Profil oder die Interessen der letzten Funktion dieser Kette? Also funktioniert nichts! Was tun? Probieren wir die verschachtelten Versprechen aus:

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

Ja ... jetzt sieht es viel ungeschickter aus als wir gehofft hatten. Ist es wegen so verrückter Nistpuppen, dass wir nicht zuletzt versucht haben, aus der Hölle der Rückrufe auszubrechen? Was ist jetzt zu tun?

Der Code kann ein wenig gekämmt werden und stützt sich auf Verschlüsse:

 //   ,     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!'); }); 

Ja, jetzt ist praktisch alles so, wie wir es wollten, aber mit einer Eigenart. Beachten Sie, wie wir Argumente in Rückrufen in den fetchedInterests fetchedUser und fetchedInterests , anstatt user und interests ? Wenn ja, dann sind Sie sehr aufmerksam!

Der Fehler dieses Ansatzes ist folgender: Sie müssen sehr, sehr vorsichtig sein, um nichts in den internen Funktionen sowie den Variablen aus dem Cache zu benennen, die Sie in Ihrem Abschluss verwenden werden. Selbst wenn Sie das Talent haben, Schattierungen zu vermeiden, scheint es immer noch ziemlich gefährlich, auf eine so hohe Variable im Verschluss zu verweisen, und das ist definitiv nicht gut.

Asynchrone Generatoren

Generatoren helfen! Wenn Sie Generatoren verwenden, verschwindet die ganze Aufregung. Nur Magie. Die Wahrheit ist. Schauen Sie nur:

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

Das ist alles. Es wird funktionieren. Sie brechen nicht in Tränen aus, wenn Sie sehen, wie schön die Generatoren sind. Bedauern Sie, dass Sie so kurzsichtig waren und Javascript gelernt haben, noch bevor die Generatoren darin erschienen sind? Ich gebe zu, eine solche Idee hat mich einmal besucht.
Aber ... wie funktioniert das alles? Wirklich magisch?

Natürlich nicht. Wir wenden uns der Belichtung zu.

Generatoren

In unserem Beispiel scheinen die Generatoren einfach zu bedienen zu sein, aber tatsächlich ist in ihnen viel los. Um mehr über asynchrone Generatoren zu erfahren, müssen Sie besser verstehen, wie Generatoren funktionieren und wie sie eine asynchrone Ausführung ermöglichen, die synchron zu sein scheint.

Wie der Name schon sagt, macht der Generator die Werte:

 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} 

Es ist ziemlich einfach, aber lassen Sie uns trotzdem darüber sprechen, was hier vor sich geht:

  1. const counter = counts(); - Initialisieren Sie den Generator und speichern Sie ihn im Variablenzähler. Der Generator befindet sich in der Schwebe, es wurde noch kein Code im Generatorkörper ausgeführt.
  2. console.log(counter.next()); - Die Interpretation der Ausgabe ( yield ) 1, nach der 1 als value und done führt zu false , da die Ausgabe dort nicht endet
  3. console.log(counter.next()); - Jetzt 2!
  4. console.log(counter.next()); - Jetzt 3! Fertig. Ist alles richtig Nein. Die Ausführung wird in Schritt yield 3; angehalten yield 3; Zum Abschluss müssen Sie next () erneut aufrufen.
  5. console.log(counter.next()); - Jetzt 4, und es wird zurückgegeben, aber nicht ausgegeben. Jetzt verlassen wir die Funktion und alles ist bereit.
  6. console.log(counter.next()); - Der Generator hat die Arbeit beendet! Er hat nichts zu berichten, außer "alles ist erledigt".

Also haben wir herausgefunden, wie Generatoren funktionieren! Aber warte, was für eine schockierende Wahrheit: Generatoren können Werte nicht nur ausspucken, sondern auch verschlingen!

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

Puh, was ?! Ein Generator verbraucht Werte, anstatt sie zu erzeugen. Wie ist das möglich?

Das Geheimnis liegt in der next Funktion. Es gibt nicht nur Werte vom Generator zurück, sondern kann sie auch an den Generator zurückgeben. Wenn Sie next() Argument mitteilen, führt die yield , auf die der Generator gerade wartet, tatsächlich zum Argument. Aus diesem Grund wird die erste counter.next(1) als undefined registriert. Es gibt einfach keine Auslieferung, die gelöst werden könnte.

Es ist so, als ob der Generator dem aufrufenden Code (Prozedur) und dem Generatorcode (Prozedur) erlaubt hätte, miteinander zusammenzuarbeiten, so dass sie sich gegenseitig Werte übergeben, während sie ausgeführt wurden, und aufeinander warten. Die Situation ist praktisch dieselbe, als ob für Javascript-Generatoren die Möglichkeit, kooperativ wettbewerbsfähig ausgeführte Verfahren zu implementieren, sie wären auch „Coroutinen“, gedacht worden wäre. Eigentlich ziemlich ähnlich wie co() , oder?

Aber beeilen wir uns nicht, sonst überlisten wir uns. In diesem Fall ist es wichtig, dass der Leser die Essenz von Generatoren und asynchroner Programmierung intuitiv erfasst. Der beste Weg, dies zu tun, besteht darin, den Generator selbst zusammenzubauen. Schreiben Sie keine Generatorfunktion und verwenden Sie nicht die fertige, sondern erstellen Sie das Innere der Generatorfunktion selbst neu.

Das interne Gerät des Generators - wir erzeugen Generatoren

Okay, ich weiß wirklich nicht, wie genau die Interna des Generators in verschiedenen JS-Laufzeiten aussehen. Das ist aber nicht so wichtig. Generatoren entsprechen der Schnittstelle. Ein „Konstruktor“ zum Instanziieren eines Generators, die next(value? : any) Methode, mit der wir den Generator anweisen, weiter zu arbeiten und ihm Werte zu geben, eine andere throw(error) Methode, falls anstelle eines Werts ein throw(error) generiert wird, und schließlich eine Methode return() , das immer noch still ist. Wenn die Einhaltung der Schnittstelle erreicht ist, ist alles in Ordnung.

Versuchen wir also, den oben genannten count counts() Generator auf reinem ES5 ohne die Schlüsselwortfunktion function* aufzubauen. Im Moment können Sie throw() ignorieren und den Wert an next() , da die Methode keine Eingaben akzeptiert. Wie kann man das machen?

In Javascript gibt es jedoch einen anderen Mechanismus zum Anhalten und Fortsetzen der Programmausführung: Schließungen! Kommt es mir bekannt vor?

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

Wenn Sie zuvor Verschlüsse verwendet haben, haben Sie sicher schon so etwas geschrieben. Die von makeCounter zurückgegebene Funktion kann genau wie ein Generator eine unendliche Folge von Zahlen erzeugen.

Diese Funktion entspricht jedoch nicht der Generatorschnittstelle und kann in unserem Beispiel nicht direkt mit counts() angewendet werden, das 4 Werte zurückgibt und beendet. Was ist für einen universellen Ansatz zum Schreiben von generatorähnlichen Funktionen erforderlich?

Schließungen, Zustandsmaschinen und harte Arbeit!

 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} 

Wenn Sie diesen Code ausführen, werden dieselben Ergebnisse wie in der Version mit dem Generator angezeigt. Schön, oder?
Also haben wir die generierende Seite des Generators aussortiert; Lassen Sie uns den Verbrauch analysieren.
In der Tat gibt es nicht viele Unterschiede.

 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); // ! 

Alles, was benötigt wird, ist das Hinzufügen von input als go Argument, und die Werte werden weitergeleitet. Sieht wieder nach Magie aus? Fast wie Generatoren?

Hurra! Also haben wir den Generator als Lieferant und als Verbraucher nachgebaut. Warum nicht versuchen, diese Funktionen darin zu kombinieren? Hier ist ein weiteres ziemlich künstliches Beispiel für einen Generator:

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

Da wir alle Spezialisten für Generatoren sind, verstehen wir, dass dieser Generator den in next(value) angegebenen next(value) zur sum addiert und dann die Summe zurückgibt. Es funktioniert genau so, wie wir es erwartet hatten:

 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. Schreiben wir nun diese Schnittstelle als normale Funktion!

 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, wir haben eine vollwertige Coroutine implementiert.

Über den Betrieb von Generatoren gibt es noch etwas zu besprechen. Wie funktionieren Ausnahmen? Mit den Ausnahmen, die innerhalb der Generatoren auftreten, ist alles einfach: next() die Ausnahme den Aufrufer erreichen und der Generator stirbt ab. Das Übergeben einer Ausnahme an den Generator erfolgt in der Methode throw() , die wir oben weggelassen haben.

Lassen Sie uns unseren Terminator mit einer coolen neuen Funktion bereichern. Wenn der Aufrufer die Ausnahme an den Generator übergibt, kehrt er zum letzten Wert der Summe zurück.

 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 

Programmierproblem - Generatorfehlerdurchdringung

Genosse, wie implementieren wir throw ()?

Einfach! Fehler ist nur ein weiterer Wert. Wir können es als nächstes Argument an go() . In der Tat ist hier einige Vorsicht geboten. Wenn throw(e) aufgerufen wird, funktioniert die yield so, als hätten wir throw e geschrieben. Dies bedeutet, dass wir jeden Zustand unserer Zustandsmaschine auf Fehler prüfen und das Programm zum Absturz bringen müssen, wenn wir den Fehler nicht behandeln können.

Beginnen wir mit der vorherigen Implementierung des kopierten Terminators

Muster

Lösung

Boom! Wir haben eine Reihe von Coroutinen implementiert, die Nachrichten und Ausnahmen wie ein echter Generator aneinander weitergeben können.

Aber die Situation wird immer schlimmer, nicht wahr? Die Implementierung der Zustandsmaschine entfernt sich zunehmend von der Implementierung des Generators. Nicht nur, dass der Code aufgrund der Fehlerbehandlung Müll ist. Der Code ist aufgrund der langen while , die wir hier haben, umso komplizierter. Um eine while zu konvertieren while Sie sie in Zustände „entwirren“. Unser Fall 1 enthält also tatsächlich 2,5 Iterationen der while , da die yield in der Mitte bricht. Schließlich müssen Sie zusätzlichen Code hinzufügen, um Ausnahmen vom Aufrufer zu übertragen und umgekehrt, wenn der Generator keinen try/catch , um diese Ausnahme zu behandeln.

Du hast es geschafft !!! Wir haben eine detaillierte Analyse möglicher Alternativen für die Implementierung von Generatoren durchgeführt, und ich hoffe, Sie haben die Funktionsweise der Generatoren bereits besser verstanden. Im trockenen Rückstand:

  • Ein Generator kann Werte generieren, Werte verbrauchen oder beides.
  • Der Zustand des Generators kann angehalten werden (Zustand, Zustandsmaschine, Fang?)
  • Der Anrufer und der Generator ermöglichen es Ihnen, eine Reihe von Corutin zu bilden, die miteinander interagieren
  • Ausnahmen werden in jede Richtung weitergeleitet.

Nachdem wir die Generatoren besser verstanden haben, schlage ich eine potenziell bequeme Argumentationsmethode vor: Dies sind syntaktische Konstruktionen, mit denen Sie wettbewerbsfähig ausgeführte Prozeduren schreiben können, die Werte über einen Kanal aneinander übergeben, der Werte einzeln übergibt ( yield ). Dies wird im nächsten Abschnitt nützlich sein, in dem wir eine Implementierung von co() aus Coroutine erstellen.

Corutin-Kontrollinversion

Nachdem wir nun mit Generatoren vertraut sind, wollen wir uns überlegen, wie sie in der asynchronen Programmierung verwendet werden können. Wenn wir Generatoren als solche schreiben können, bedeutet dies nicht, dass Versprechen in Generatoren automatisch gelöst werden. Aber warten Sie, Generatoren sollen nicht alleine arbeiten. Sie müssen mit einem anderen Programm interagieren, der Hauptprozedur, die .next() und .throw() .

Was ist, wenn wir unsere Geschäftslogik nicht in das Hauptverfahren, sondern in den Generator einfügen? Immer wenn ein bestimmter asynchroner Wert wie ein Versprechen für die Geschäftslogik auftritt, sagt der Generator: "Ich möchte mich nicht mit diesem Unsinn anlegen, wecke mich, wenn er sich auflöst", hält an und gibt ein Versprechen an das Serving-Verfahren aus. Wartungsverfahren: "OK, ich rufe Sie später an." Danach registriert es einen Rückruf mit diesem Versprechen, wird beendet und wartet, bis es möglich ist, einen Zyklus von Ereignissen auszulösen (dh wenn das Versprechen aufgelöst wird). In diesem .next() meldet die Prozedur "Hey, Sie sind dran" und sendet den Wert über .next() Schlafgenerator. Sie wird warten, bis der Generator seine Arbeit erledigt hat, und in der Zwischenzeit wird er andere asynchrone Dinge tun ... und so weiter. Sie haben eine traurige Geschichte darüber gehört, wie das Verfahren im Dienste eines Generators weitergeht.

Also zurück zum Hauptthema. Jetzt, da wir wissen, wie Generatoren und Versprechen funktionieren, wird es für uns nicht schwierig sein, ein solches „Serviceverfahren“ zu erstellen. Die Serviceprozedur selbst wird als Versprechen wettbewerbsfähig ausgeführt, instanziiert und wartet den Generator und kehrt dann mit dem Rückruf .then() zum Endergebnis unserer Hauptprozedur zurück.

Kehren wir als nächstes zum Programm co () zurück und diskutieren es ausführlicher. co() ist eine Serviceprozedur, die Slave-Arbeit übernimmt, sodass der Generator nur mit synchronen Werten arbeiten kann. Sieht schon viel logischer aus, oder?

 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()

Großartig! 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) => { //   }); } 

Lösung

, ? - 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) => { //   }); } 

Lösung

. , , .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 ) .

Das Ende

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

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


All Articles