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:
- JavaScript-Engines und globaler Speicher
- JavaScript-Engines: Wie funktionieren sie? Globaler Ausführungskontext und Aufrufstapel
- JavaScript ist Single-Threaded und andere lustige Geschichten
- Asynchrones JavaScript, Rückrufwarteschlange und Ereignisschleife
- Rückruf Hölle und verspricht ES6
- Erstellen und Arbeiten mit JavaScript-Versprechen
- Fehlerbehandlung in ES6-Versprechungen
- ES6 Promise-Kombinatoren: Promise.all, Promise.allSettled, Promise.any und andere
- ES6 verspricht und Mikrotask-Warteschlange
- JavaScript-Engines: Wie funktionieren sie? Asynchrone Evolution: von Versprechen zu Async / Warten
- 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:
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) {
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);
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"
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
: , , .