JavaScript-Engines: Wie funktionieren sie? Vom Call-Stack bis zu den Versprechungen (fast) alles, was Sie wissen müssen


Haben Sie sich jemals gefragt, wie Browser JavaScript-Code lesen und ausführen? Es sieht mysteriös aus, aber in diesem Beitrag können Sie eine Vorstellung davon bekommen, was unter der Haube passiert.

Wir beginnen unsere Reise in die Sprache mit einem Ausflug in die wunderbare Welt der JavaScript-Engines.

Öffnen Sie die Konsole in Chrome und wechseln Sie zur Registerkarte Quellen. Sie werden mehrere Abschnitte sehen, und einer der interessantesten heißt Call Stack (in Firefox sehen Sie Call Stack, wenn Sie einen Haltepunkt in den Code einfügen):



Was ist ein Call Stack? Es scheint viel los zu sein, auch um ein paar Codezeilen auszuführen. Tatsächlich wird JavaScript nicht in jedem Browser mitgeliefert. Es gibt eine große Komponente, die unseren JavaScript-Code kompiliert und interpretiert - es ist eine JavaScript-Engine. Am beliebtesten sind V8, es wird in Google Chrome und Node.js verwendet, SpiderMonkey in Firefox, JavaScriptCore in Safari / WebKit.

JavaScript-Engines sind heute hervorragende Beispiele für Software-Engineering, und es wird fast unmöglich sein, über alle Aspekte zu sprechen. Die Hauptarbeit zur Codeausführung wird für uns jedoch nur von wenigen Komponenten der Engines erledigt: Call Stack (Call Stack), Global Memory (Global Memory) und Execution Context (Ausführungskontext). Bereit sie zu treffen?

Inhalt:

  1. JavaScript-Engines und globaler Speicher
  2. JavaScript-Engines: Wie funktionieren sie? Globaler Ausführungskontext und Aufrufstapel
  3. JavaScript ist Single-Threaded und andere lustige Geschichten
  4. Asynchrones JavaScript, Rückrufwarteschlange und Ereignisschleife
  5. Rückruf Hölle und verspricht ES6
  6. Erstellen und Arbeiten mit JavaScript-Versprechen
  7. Fehlerbehandlung in ES6-Versprechungen
  8. ES6 Promise-Kombinatoren: Promise.all, Promise.allSettled, Promise.any und andere
  9. ES6 verspricht und Mikrotask-Warteschlange
  10. JavaScript-Engines: Wie funktionieren sie? Asynchrone Evolution: von Versprechen zu Async / Warten
  11. JavaScript-Engines: Wie funktionieren sie? Zusammenfassung

1. JavaScript-Engines und globaler Speicher


Ich sagte, dass JavaScript sowohl eine kompilierte als auch eine interpretierte Sprache ist. Ob Sie es glauben oder nicht, JavaScript-Engines kompilieren Ihren Code tatsächlich Mikrosekunden, bevor er ausgeführt wird.

Eine Art Magie, oder? Diese Magie heißt JIT (Just in Time Compilation). Es allein ist ein großes Diskussionsthema, selbst Bücher werden nicht ausreichen, um die Arbeit von JIT zu beschreiben. Aber jetzt werden wir die Theorie überspringen und uns auf die Ausführungsphase konzentrieren, was nicht weniger interessant ist.

Schauen Sie sich zunächst diesen Code an:

var num = 2; function pow(num) { return num * num; } 

Angenommen, ich frage Sie, wie dieser Code in einem Browser verarbeitet wird. Was wirst du antworten? Sie können sagen: "Der Browser liest den Code" oder "Der Browser führt den Code aus". In Wirklichkeit ist nicht alles so einfach. Zunächst wird der Code nicht vom Browser, sondern von der Engine gelesen. Die JavaScript-Engine liest den Code und fügt , sobald sie die erste Zeile definiert, einige Links in den globalen Speicher ein .

Der globale Speicher (auch Heap genannt) ist der Bereich, in dem die JavaScript-Engine Variablen und Funktionsdeklarationen speichert. Und wenn er den obigen Code liest, erscheinen zwei Ordner im globalen Speicher:



Auch wenn das Beispiel nur eine Variable und eine Funktion enthält, stellen Sie sich vor, dass Ihr JavaScript-Code in einer größeren Umgebung ausgeführt wird: in einem Browser oder in Node.js. In solchen Umgebungen gibt es viele vordefinierte Funktionen und Variablen, die als global bezeichnet werden. Daher enthält der globale Speicher viel mehr Daten als nur num und pow .

Im Moment läuft nichts. Versuchen wir nun, unsere Funktion auszuführen:

 var num = 2; function pow(num) { return num * num; } pow(num); 

Was wird passieren? Und etwas Interessantes wird passieren. Beim Aufrufen der Funktion hebt die JavaScript-Engine zwei Abschnitte hervor:

  • Globaler Ausführungskontext
  • Stapel aufrufen

Was sind sie?

2. JavaScript-Engines: Wie funktionieren sie? Globaler Ausführungskontext und Aufrufstapel


Sie haben gelernt, wie die JavaScript-Engine Variablen und Funktionsdeklarationen liest. Sie fallen in den globalen Speicher (Heap).

Aber jetzt führen wir eine JavaScript-Funktion aus, und die Engine sollte sich darum kümmern. Auf welche Weise? Jede JavaScript-Engine verfügt über eine Schlüsselkomponente, die als Aufrufstapel bezeichnet wird .

Dies ist eine gestapelte Datenstruktur : Elemente können von oben hinzugefügt werden, sie können jedoch nicht von der Struktur ausgeschlossen werden, solange sich andere Elemente darüber befinden. So funktionieren JavaScript-Funktionen. Bei der Ausführung können sie den Aufrufstapel nicht verlassen, wenn eine andere Funktion darin vorhanden ist. Beachten Sie dies, da dieses Konzept zum Verständnis der Aussage "JavaScript ist Single-Threaded" beiträgt.

Aber zurück zu unserem Beispiel. Wenn eine Funktion aufgerufen wird, sendet die Engine sie an den Aufrufstapel :



Ich präsentiere den Call-Stack gerne als Stapel von Pringles-Chips. Wir können keine Chips vom Boden des Stapels essen, bis wir diejenigen essen, die oben sind. Glücklicherweise ist unsere Funktion synchron: Es ist nur eine Multiplikation, die schnell berechnet wird.

Gleichzeitig platziert die Engine den globalen Ausführungskontext im Speicher. Dies ist die globale Umgebung, in der JavaScript-Code ausgeführt wird. So sieht es aus:



Stellen Sie sich einen globalen Ausführungskontext in Form eines Meeres vor, in dem globale JavaScript-Funktionen wie Fische schweben. Wie süß! Dies ist jedoch nur die halbe Wahrheit. Was ist, wenn unsere Funktion verschachtelte Variablen oder interne Funktionen hat?

Selbst im einfachen Fall, wie unten gezeigt, erstellt die JavaScript-Engine einen lokalen Ausführungskontext :

 var num = 2; function pow(num) { var fixed = 89; return num * num; } pow(num); 

Beachten Sie, dass ich die fixed Variable zur pow Funktion hinzugefügt habe. In diesem Fall enthält der lokale Ausführungskontext einen Abschnitt für fixed . Ich bin nicht sehr gut darin, kleine Rechtecke in andere kleine Rechtecke zu zeichnen. Verwenden Sie also Ihre Fantasie.

Ein lokaler Ausführungskontext wird neben pow im grünen Rechteckbereich im globalen Ausführungskontext angezeigt. Stellen Sie sich auch vor, wie die Engine für jede verschachtelte Funktion innerhalb der verschachtelten Funktion andere lokale Ausführungskontexte erstellt. Alle diese Rechteckabschnitte erscheinen sehr schnell! Wie eine Nistpuppe!

Kommen wir zurück zur Single-Threaded-Geschichte. Was bedeutet das?

3. JavaScript ist Single-Threaded und andere lustige Geschichten


Wir sagen, dass JavaScript Single-Threaded ist, da nur ein Aufrufstapel unsere Funktionen übernimmt . Ich möchte Sie daran erinnern, dass Funktionen den Aufrufstapel nicht verlassen können, wenn andere Funktionen die Ausführung erwarten.

Dies ist kein Problem, wenn wir mit synchronem Code arbeiten. Beispielsweise ist die Addition von zwei Zahlen synchron und wird in Mikrosekunden berechnet. Was ist mit Netzwerkanrufen und anderen Interaktionen mit der Außenwelt?

Glücklicherweise sind JavaScript-Engines standardmäßig so konzipiert, dass sie asynchron arbeiten . Selbst wenn sie jeweils nur eine Funktion ausführen können, können langsamere Funktionen von einer externen Entität ausgeführt werden - in unserem Fall handelt es sich um einen Browser. Wir werden weiter unten darüber sprechen.

Gleichzeitig wissen Sie, dass die Engine diesen Code zeilenweise liest und die folgenden Schritte ausführt, wenn der Browser eine Art JavaScript-Code lädt:

  • Fügt Variablen und Funktionsdeklarationen in den globalen Speicher (Heap) ein.
  • Sendet einen Aufruf an jede Funktion im Aufrufstapel.
  • Erstellt einen globalen Ausführungskontext, in dem globale Funktionen ausgeführt werden.
  • Erstellt viele kleine lokale Ausführungskontexte (wenn interne Variablen oder verschachtelte Funktionen vorhanden sind).

Sie haben jetzt ein grundlegendes Verständnis der Synchronisationsmechanik, die allen JavaScript-Engines zugrunde liegt. Im nächsten Kapitel werden wir darüber sprechen, wie asynchroner Code in JavaScript funktioniert und warum dies so funktioniert.

4. Asynchrones JavaScript, Rückrufwarteschlange und Ereignisschleife


Dank des globalen Speichers, des Ausführungskontexts und des Aufrufstapels wird synchroner JavaScript-Code in unseren Browsern ausgeführt. Aber wir haben etwas vergessen. Was passiert, wenn Sie eine asynchrone Funktion ausführen müssen?

Mit asynchroner Funktion meine ich jede Interaktion mit der Außenwelt, deren Abschluss einige Zeit in Anspruch nehmen kann. Das Aufrufen der REST-API oder des Timers erfolgt asynchron, da die Ausführung Sekunden dauern kann. Dank der in der Engine verfügbaren Elemente können wir solche Funktionen verarbeiten, ohne den Aufrufstapel und den Browser zu blockieren. Vergessen Sie nicht, dass der Aufrufstapel jeweils nur eine Funktion ausführen kann und sogar eine Blockierungsfunktion den Browser buchstäblich stoppen kann . Glücklicherweise sind JavaScript-Engines intelligent und können mit ein wenig Hilfe des Browsers Abhilfe schaffen.

Wenn wir eine asynchrone Funktion ausführen, nimmt der Browser sie und führt sie für uns aus. Nehmen Sie einen Timer wie diesen:

 setTimeout(callback, 10000); function callback(){ console.log('hello timer!'); } 

Ich bin mir sicher, dass Sie, obwohl Sie setTimeout hunderte Male gesehen haben, möglicherweise nicht wissen, dass diese Funktion nicht in JavaScript integriert ist . Als JavaScript setTimeout , war darin keine setTimeout Funktion enthalten. Tatsächlich ist es Teil der sogenannten Browser-APIs, einer Sammlung praktischer Tools, die der Browser uns zur Verfügung stellt. Wunderbar! Aber was bedeutet das in der Praxis? Da sich setTimeout auf die Browser-API bezieht, wird diese Funktion vom Browser selbst ausgeführt (für einen Moment erscheint sie im Aufrufstapel, wird aber sofort von dort gelöscht).

Nach 10 Sekunden übernimmt der Browser die Rückruffunktion, die wir an ihn übergeben haben, und stellt sie in die Rückrufwarteschlange . Im Moment sind zwei weitere Rechteckabschnitte in der JavaScript-Engine erschienen. Schauen Sie sich diesen Code an:

 var num = 2; function pow(num) { return num * num; } pow(num); setTimeout(callback, 10000); function callback(){ console.log('hello timer!'); } 

Jetzt sieht unser Schema so aus:



setTimeout wird im Browserkontext ausgeführt. Nach 10 Sekunden startet der Timer und die Rückruffunktion ist zur Ausführung bereit. Aber zuerst muss es die Rückrufwarteschlange durchlaufen. Dies ist eine Datenstruktur in Form einer Warteschlange und, wie der Name schon sagt, eine geordnete Funktionswarteschlange.

Jede asynchrone Funktion muss eine Rückrufwarteschlange durchlaufen, bevor sie in den Aufrufstapel gelangt. Aber wer sendet die Funktionen als nächstes? Dadurch wird eine Komponente als Ereignisschleife bezeichnet .

Bisher behandelt die Ereignisschleife nur eines: Sie prüft, ob der Aufrufstapel leer ist. Wenn sich in der Rückrufwarteschlange eine Funktion befindet und der Aufrufstapel frei ist, ist es Zeit, einen Rückruf an den Aufrufstapel zu senden.

Danach gilt die Funktion als ausgeführt. Dies ist das allgemeine Schema für die Verarbeitung von asynchronem und synchronem Code mit der JavaScript-Engine:



Angenommen, callback() ist zur Ausführung bereit. Wenn pow() der Aufrufstapel freigegeben und die Ereignisschleife sendet callback() . Und alle! Obwohl ich die Dinge ein wenig vereinfacht habe, können Sie, wenn Sie das obige Diagramm verstehen, alles JavaScript verstehen.

Denken Sie daran: Browserbasierte APIs, Rückrufwarteschlangen und Ereignisschleifen sind die Säulen von asynchronem JavaScript .

Und wenn Sie interessiert sind, können Sie sich das kuriose Video „Was zum Teufel ist die Event-Schleife überhaupt?“ Von Philip Roberts ansehen. Dies ist eine der besten Erklärungen für die Ereignisschleife.

Das asynchrone JavaScript-Thema ist jedoch noch nicht fertig. In den folgenden Kapiteln werden wir ES6-Versprechen betrachten.

5. Callback Hell und ES6 Versprechen


Rückruffunktionen werden in JavaScript überall verwendet, sowohl im synchronen als auch im asynchronen Code. Betrachten Sie diese Methode:

 function mapper(element){ return element * 2; } [1, 2, 3, 4, 5].map(mapper); 

mapper ist eine Rückruffunktion, die innerhalb der map . Der obige Code ist synchron. Betrachten Sie nun dieses Intervall:

 function runMeEvery(){ console.log('Ran!'); } setInterval(runMeEvery, 5000); 

Dieser Code ist asynchron, da wir in setInterval den runMeEvery-Rückruf übergeben. Rückrufe werden in JavaScript verwendet, daher haben wir seit Jahren ein Problem namens "Rückrufhölle" - "Rückrufhölle".

Der Begriff Rückrufhölle in JavaScript wird auf den "Programmierstil" angewendet, bei dem Rückrufe in andere Rückrufe eingebettet sind, die in andere Rückrufe eingebettet sind ... Aufgrund der Asynchronität sind JavaScript-Programmierer seit langem in diese Falle geraten.

Um ehrlich zu sein, habe ich nie große Pyramiden von Rückrufen geschaffen. Vielleicht, weil ich lesbaren Code schätze und immer versuche, mich an seine Prinzipien zu halten. Wenn Sie die Callback-Hölle erreichen, bedeutet dies, dass Ihre Funktion zu viel leistet.

Ich werde nicht im Detail über die Rückrufhölle sprechen. Wenn Sie interessiert sind, gehen Sie zu callbackhell.com , wo dieses Problem im Detail untersucht und verschiedene Lösungen vorgeschlagen wurden. Und wir werden über ES6-Versprechen sprechen. Dies ist ein JavaScript-Addon, das entwickelt wurde, um das Problem des Rückrufs zu lösen. Aber was sind Versprechen?

Ein JavaScript-Versprechen ist eine Darstellung eines zukünftigen Ereignisses . Ein Versprechen kann erfolgreich enden oder in einem Jargon von Programmierern wird ein Versprechen „gelöst“ (gelöst). Wenn das Versprechen jedoch mit einem Fehler endet, sagen wir, dass es sich im abgelehnten Zustand befindet. Versprechen haben auch einen Standardstatus: Jedes neue Versprechen beginnt in einem ausstehenden Status. Kann ich mein eigenes Versprechen machen? Ja Wir werden im nächsten Kapitel darüber sprechen.

6. Erstellen und Arbeiten mit JavaScript-Versprechen


Um ein neues Versprechen zu erstellen, müssen Sie den Konstruktor aufrufen, indem Sie ihm eine Rückruffunktion übergeben. Es können nur zwei Parameter verwendet werden: resolve und reject . Lassen Sie uns ein neues Versprechen erstellen, das in 5 Sekunden gelöst wird (Sie können die Beispiele in der Browserkonsole testen):

 const myPromise = new Promise(function(resolve){ setTimeout(function(){ resolve() }, 5000) }); 

Wie Sie sehen können, ist resolve eine Funktion, die wir aufrufen, damit das Versprechen erfolgreich endet. Und reject schafft ein abgelehntes Versprechen:

 const myPromise = new Promise(function(resolve, reject){ setTimeout(function(){ reject() }, 5000) }); 

Beachten Sie, dass Sie die reject ignorieren können, da dies der zweite Parameter ist. Wenn Sie jedoch reject , können Sie die resolve nicht ignorieren . Das heißt, der folgende Code funktioniert nicht und endet mit einem zulässigen Versprechen:

 // Can't omit resolve ! const myPromise = new Promise(function(reject){ setTimeout(function(){ reject() }, 5000) }); 

Versprechen sehen momentan nicht so nützlich aus, oder? Diese Beispiele zeigen dem Benutzer nichts an. Fügen wir etwas hinzu. Und erlaubte, abgelehnte Versprechen können Daten zurückgeben. Zum Beispiel:

 const myPromise = new Promise(function(resolve) { resolve([{ name: "Chris" }]); }); 

Aber wir sehen immer noch nichts. Um Daten aus einem Versprechen zu extrahieren, müssen Sie das Versprechen der then Methode zuordnen . Er nimmt einen Rückruf entgegen (was für eine Ironie!), Der die aktuellen Daten erhält:

 const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); myPromise.then(function(data) { console.log(data); }); 

Als JavaScript-Entwickler und Konsument des Codes anderer Leute interagieren Sie hauptsächlich mit externen Versprechungen. Bibliotheksersteller verpacken Legacy-Code am häufigsten wie folgt in einen Promise-Konstruktor:

 const shinyNewUtil = new Promise(function(resolve, reject) { // do stuff and resolve // or reject }); 

Bei Bedarf können wir ein Versprechen auch erstellen und auflösen, indem wir Promise.resolve() aufrufen:

 Promise.resolve({ msg: 'Resolve!'}) .then(msg => console.log(msg)); 

Ich möchte Sie daran erinnern: JavaScript-Versprechen sind ein Lesezeichen für ein Ereignis, das in Zukunft stattfinden wird. Ein Ereignis beginnt im Status „Warten auf eine Entscheidung“ und kann erfolgreich (zulässig, ausgeführt) oder erfolglos (abgelehnt) sein. Ein Versprechen kann Daten zurückgeben, die durch Anhängen abgerufen werden können. Im nächsten Kapitel werden wir diskutieren, wie mit Fehlern aus Versprechungen umgegangen werden kann.

7. Fehlerbehandlung in ES6-Versprechungen


Der Umgang mit Fehlern in JavaScript war immer einfach, zumindest im synchronen Code. Schauen Sie sich ein Beispiel an:

 function makeAnError() { throw Error("Sorry mate!"); } try { makeAnError(); } catch (error) { console.log("Catching the error! " + error); } 

Das Ergebnis wird sein:

 Catching the error! Error: Sorry mate! 

Wie erwartet fiel der Fehler in den catch . Versuchen Sie nun die asynchrone Funktion:

 function makeAnError() { throw Error("Sorry mate!"); } try { setTimeout(makeAnError, 5000); } catch (error) { console.log("Catching the error! " + error); } 

Dieser Code ist aufgrund von setTimeout asynchron. Was passiert, wenn wir es ausführen?

  throw Error("Sorry mate!"); ^ Error: Sorry mate! at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9) 

Jetzt ist das Ergebnis anders. Der Fehler wurde nicht vom catch abgefangen, sondern stieg frei auf den Stapel. Der Grund ist, dass try/catch nur mit synchronem Code funktioniert. Wenn Sie mehr wissen möchten, wird dieses Problem hier ausführlich behandelt.

Glücklicherweise können wir mit Versprechungen asynchrone Fehler so behandeln, als wären sie synchron. Im letzten Kapitel habe ich gesagt, dass das Aufrufen der reject zu einer Ablehnung des Versprechens führt:

 const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry!'); }); 

In diesem Fall können wir Fehler mit dem catch Handler behandeln, indem wir (erneut) einen Rückruf abrufen:

 const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry!'); }); myPromise.catch(err => console.log(err)); 

Um ein Versprechen an der richtigen Stelle zu erstellen und abzulehnen, können Sie außerdem Promise.reject() aufrufen:

 Promise.reject({msg: 'Rejected!'}).catch(err => console.log(err)); 

Ich möchte Sie daran erinnern, dass der Then-Handler ausgeführt wird, wenn das Versprechen ausgeführt wird, und der catch Handler für abgelehnte Versprechen ausgeführt wird. Dies ist jedoch nicht das Ende der Geschichte. Unten sehen wir, wie gut async/await mit try/catch funktioniert.

8. Kombinatoren von ES6-Versprechungen: Promise.all, Promise.allSettled, Promise.any und andere


Versprechen sind nicht dazu gedacht, alleine zu arbeiten. Die Promise-API bietet eine Reihe von Methoden zum Kombinieren von Versprechen . Eines der nützlichsten ist Promise.all , es nimmt ein Array von Versprechungen und gibt ein Versprechen zurück. Das einzige Problem ist, dass Promise.all abgelehnt wird, wenn mindestens ein Versprechen im Array abgelehnt wird.

Promise.race erlaubt oder lehnt ab, sobald eines der Versprechen im Array den entsprechenden Status erhält.

In neueren Versionen von V8 werden außerdem zwei neue Kombinatoren eingeführt: Promise.allSettled und Promise.any . Promise.any befindet sich noch in einem frühen Stadium der vorgeschlagenen Funktionalität. Zum Zeitpunkt des Schreibens wird dieser Artikel nicht unterstützt. Theoretisch kann er jedoch signalisieren, ob ein Versprechen erfüllt wurde. Der Unterschied zu Promise.race besteht darin, dass Promise.any nicht abgelehnt wird, selbst wenn eines der Versprechen abgelehnt wird .

Promise.allSettled noch interessanter. Er nimmt auch eine Reihe von Versprechungen an, „verkürzt“ sich jedoch nicht, wenn eines der Versprechen abgelehnt wird. Dies ist nützlich, wenn Sie überprüfen müssen, ob alle Versprechen in einem Array zu einem bestimmten Zeitpunkt übergegangen sind, unabhängig davon, ob abgelehnte Versprechen vorhanden sind. Es kann als das Gegenteil von Promise.all .

9. ES6-Versprechen und die Mikrotask-Warteschlange


Wenn Sie sich aus dem vorherigen Kapitel erinnern, befindet sich jede asynchrone Rückruffunktion in JavaScript in der Rückrufwarteschlange, bevor sie den Aufrufstapel erreicht. An das Versprechen übergebene Rückruffunktionen haben jedoch ein anderes Schicksal: Sie werden eher von der Mikrotask-Warteschlange als von der Task-Warteschlange verarbeitet.

Und hier müssen Sie vorsichtig sein: Die Mikrotask-Warteschlange steht vor der Anrufwarteschlange . Rückrufe aus der Mikrotask-Warteschlange haben Vorrang, wenn die Ereignisschleife prüft, ob neue Rückrufe für den Aufrufstapel bereit sind.

Diese Mechanik wird von Jake Archibald in Aufgaben, Mikrotasks, Warteschlangen und Zeitplänen ausführlicher beschrieben.

10. JavaScript-Engines: Wie funktionieren sie? Asynchrone Evolution: von Versprechen zu Async / Warten


JavaScript entwickelt sich rasant weiter und wir verbessern uns jedes Jahr ständig Versprechen sahen aus wie ein Finale, aber mit ECMAScript 2017 (ES8) erschien eine neue Syntax: async/await .

async/await ist nur eine stilistische Verbesserung, die wir syntaktischen Zucker nennen. async/await ändert JavaScript in keiner Weise (vergessen Sie nicht, dass die Sprache mit älteren Browsern abwärtskompatibel sein und vorhandenen Code nicht beschädigen sollte). Dies ist nur eine neue Methode, um asynchronen Code basierend auf Versprechungen zu schreiben. Betrachten Sie ein Beispiel. Oben haben wir das Versprechen bereits im entsprechenden then gespeichert:

 const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); myPromise.then((data) => console.log(data)) 

Jetzt können wir mit async/await den asynchronen Code so verarbeiten, dass der Code für den Leser unserer Auflistung synchron aussieht . Anstatt then wir das Versprechen in eine Funktion mit der Bezeichnung async , und dann await wir await Ergebnis:

 const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); async function getData() { const data = await myPromise; console.log(data); } getData(); 

Sieht gut aus, oder? Es ist lustig, dass eine asynchrone Funktion immer ein Versprechen zurückgibt, und niemand kann es daran hindern:

 async function getData() { const data = await myPromise; return data; } getData().then(data => console.log(data)); 

Was ist mit Fehlern? Einer der Vorteile von async/await ist, dass wir mit dieser Konstruktion try/catch . Lesen Sie die Einführung zur Fehlerbehandlung in asynchronen Funktionen und deren Tests .

Schauen wir uns noch einmal das Versprechen an, in dem wir Fehler mit dem catch Handler behandeln:

 const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry!'); }); myPromise.catch(err => console.log(err)); 

Mit asynchronen Funktionen können wir Folgendes umgestalten:

 async function getData() { try { const data = await myPromise; console.log(data); // or return the data with return data } catch (error) { console.log(error); } } getData(); 

Allerdings hat nicht jeder zu diesem Stil gewechselt. try/catch kann Ihren Code komplizieren. Es gibt noch eine Sache zu beachten. Sehen Sie in diesem Code, wie ein Fehler in diesem try Block auftritt:

 async function getData() { try { if (true) { throw Error("Catch me if you can"); } } catch (err) { console.log(err.message); } } getData() .then(() => console.log("I will run no matter what!")) .catch(() => console.log("Catching err")); 

Was ist mit den zwei Zeilen, die in der Konsole angezeigt werden? Denken Sie daran, dass try/catch ein synchrones Konstrukt ist und unsere asynchrone Funktion ein Versprechen generiert . Sie folgen zwei verschiedenen Wegen, wie Züge. Aber sie werden sich niemals treffen! , throw , catch getData() . , «Catch me if you can», «I will run no matter what!».

, throw then . , , Promise.reject() :

 async function getData() { try { if (true) { return Promise.reject("Catch me if you can"); } } catch (err) { console.log(err.message); } } Now the error will be handled as expected: getData() .then(() => console.log("I will NOT run no matter what!")) .catch(() => console.log("Catching err")); "Catching err" // output 

async/await JavaScript. .

, JS- async/await . . , async/await — .

11. JavaScript-: ?


JavaScript — , , . JS-: V8, Google Chrome Node.js; SpiderMonkey, Firefox; JavaScriptCore, Safari.

JavaScript- «» : , , , . , .

JavaScript- , . JavaScript: , - , (, ) .

ECMAScript 2015 . — , . . 2017- async/await : , , .

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


All Articles