Als Single-Thread-Sprache war JavaScript schon immer auf asynchrone Programmierung angewiesen, um zeitaufwändige Aufgaben zu bewältigen, ohne die Codeausführung zu blockieren. Im Laufe der Jahre haben sich die Ansätze zum Umgang mit Asynchronität in JavaScript erheblich weiterentwickelt und sind lesbarer, handhabbarer und leichter nachvollziehbar geworden. Lassen Sie mich Sie auf eine Reise durch die Geschichte des asynchronen JavaScript mitnehmen, von Rückrufen über Versprechen bis hin zu Async/Await.
In den Anfängen von JavaScript, vor der weit verbreiteten Einführung von Callbacks, wurde der meiste JavaScript-Code synchron geschrieben. Synchroner Code bedeutet, dass jede Operation nacheinander auf blockierende Weise ausgeführt wird. Wenn ein Vorgang mit langer Laufzeit festgestellt wurde, wurde die Ausführung des gesamten Skripts angehalten, bis dieser Vorgang abgeschlossen war.
Stellen Sie sich vor, Sie stehen an einem Fahrkartenschalter am Bahnhof und haben nur einen Fahrkartenverkäufer. Sie fordern ein Ticket an und der Ticketverkäufer beginnt mit der Bearbeitung Ihrer Anfrage. Im synchronen Modell müssten Sie am Schalter warten, bis der Ticketverkäufer Ihre Anfrage bearbeitet und Ihnen das Ticket aushändigt. In dieser Zeit kann niemand anderes bedient werden und der gesamte Ticketschalter ist gesperrt.
Hier ist ein Beispiel für synchronen JavaScript-Code:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
In diesem Code werden die console.log-Anweisungen der Reihe nach ausgeführt und der lang andauernde Vorgang (die for-Schleife) blockiert die Ausführung des Skripts, bis es abgeschlossen ist. Die Meldung „Nach dem Vorgang“ wird erst protokolliert, nachdem die Schleife beendet ist.
Obwohl synchroner Code einfach zu verstehen und zu begründen ist, wirft er mehrere Probleme auf, insbesondere wenn es um zeitaufwändige Vorgänge geht:
Um die Einschränkungen des synchronen Codes zu überwinden und ein besseres Benutzererlebnis zu bieten, wurden asynchrone Programmiertechniken eingeführt. Durch die asynchrone Programmierung können lang laufende Vorgänge im Hintergrund ausgeführt werden, ohne die Ausführung des restlichen Codes zu blockieren. Auf diese Weise wurde Callback eingeführt.
Rückrufe waren die primäre Möglichkeit, asynchrone Vorgänge abzuwickeln. Ein Rückruf ist einfach eine Funktion, die als Argument an eine andere Funktion übergeben wird und später ausgeführt wird, sobald der asynchrone Vorgang abgeschlossen ist.
Stellen Sie sich vor, Sie möchten eine Bahnfahrkarte kaufen. Sie gehen zum Ticketschalter am Bahnhof und beantragen ein Ticket für ein bestimmtes Ziel. Der Fahrkartenverkäufer nimmt Ihre Anfrage entgegen und bittet Sie zu warten, während er die Verfügbarkeit von Sitzplätzen im Zug prüft. Sie übermitteln ihnen Ihre Kontaktdaten und warten im Wartebereich. Sobald der Ticketverkäufer Ihre Anfrage bearbeitet hat und ein Sitzplatz verfügbar ist, ruft er Ihren Namen an, um Ihnen mitzuteilen, dass Ihr Ticket zur Abholung bereit ist. In dieser Analogie sind Ihre Kontaktinformationen der Rückruf – eine Möglichkeit für den Ticketverkäufer, Sie zu benachrichtigen, wenn die asynchrone Aufgabe (Prüfung der Sitzplatzverfügbarkeit und Ausstellung des Tickets) abgeschlossen ist.
So bezieht sich die Analogie auf Rückrufe in JavaScript:
Beim Callback-Ansatz stellen Sie eine Funktion (den Callback) bereit, die aufgerufen wird, sobald der asynchrone Vorgang abgeschlossen ist. Die asynchrone Funktion führt ihre Aufgabe aus und ruft dann den Rückruf mit dem Ergebnis oder Fehler auf, sodass Ihr Code das Ergebnis des asynchronen Vorgangs verarbeiten kann.
Hier ist ein Beispiel für einen API-Aufruf mithilfe von Rückrufen in Node.js:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
In diesem Beispiel haben wir eine fetchData-Funktion, die einen API-Aufruf simuliert. Als Argumente werden ein URL-Parameter und eine Rückruffunktion benötigt. Innerhalb der Funktion verwenden wir setTimeout, um eine Verzögerung von 1000 Millisekunden (1 Sekunde) zu simulieren, bevor die Rückruffunktion aufgerufen wird.
Die Callback-Funktion folgt der allgemeinen Konvention, einen Fehler als erstes Argument (err) und die Daten als zweites Argument (data) zu akzeptieren. In diesem Beispiel simulieren wir einen erfolgreichen API-Aufruf, indem wir den Fehler auf null setzen und ein Beispieldatenobjekt bereitstellen.
Um die fetchData-Funktion zu verwenden, rufen wir sie mit einer URL und einer Callback-Funktion auf. Innerhalb der Callback-Funktion prüfen wir zunächst, ob ein Fehler aufgetreten ist, indem wir das Argument err überprüfen. Wenn ein Fehler vorliegt, protokollieren wir ihn mit console.error in der Konsole und kehren zurück, um die weitere Ausführung zu stoppen.
Wenn kein Fehler aufgetreten ist, protokollieren wir die empfangenen Daten mithilfe von console.log in der Konsole.
Wenn Sie diesen Code ausführen, simuliert er einen asynchronen API-Aufruf. Nach einer Verzögerung von 1 Sekunde wird die Rückruffunktion aufgerufen und das Ergebnis in der Konsole protokolliert:
{ id: 1, name: 'John Doe' }
Dieses Beispiel zeigt, wie Rückrufe zur Verarbeitung asynchroner API-Aufrufe verwendet werden können. Die Rückruffunktion wird als Argument an die asynchrone Funktion (fetchData) übergeben und aufgerufen, sobald der asynchrone Vorgang abgeschlossen ist, entweder mit einem Fehler oder mit den resultierenden Daten.
Obwohl Rückrufe ihre Arbeit erledigten, hatten sie mehrere Nachteile:
Um den Herausforderungen mit Rückrufen zu begegnen, wurden in ES6 (ECMAScript 2015) Versprechen eingeführt. Ein Versprechen stellt den eventuellen Abschluss oder Misserfolg eines asynchronen Vorgangs dar und ermöglicht es Ihnen, Vorgänge miteinander zu verketten.
Stellen Sie sich ein Versprechen wie ein Zugticket vor. Wenn Sie ein Bahnticket kaufen, stellt das Ticket ein Versprechen der Eisenbahngesellschaft dar, dass Sie in den Zug einsteigen und Ihr Ziel erreichen können. Das Ticket enthält Informationen über den Zug, wie Abfahrtszeit, Strecke und Sitzplatznummer. Sobald Sie das Ticket haben, können Sie auf die Ankunft des Zuges warten und wenn dieser zum Einsteigen bereit ist, können Sie mit Ihrem Ticket in den Zug einsteigen.
In dieser Analogie ist das Zugticket das Versprechen. Es stellt den endgültigen Abschluss eines asynchronen Vorgangs (der Zugfahrt) dar. Sie behalten das Ticket (das Versprechensobjekt), bis der Zug bereit ist (der asynchrone Vorgang ist abgeschlossen). Sobald das Versprechen gelöst ist (der Zug ankommt), können Sie mit dem Ticket in den Zug einsteigen (auf den gelösten Wert zugreifen).
So bezieht sich die Analogie auf Versprechen in JavaScript:
Versprechen bieten eine strukturierte Möglichkeit, asynchrone Vorgänge abzuwickeln, sodass Sie mehrere Vorgänge miteinander verketten und Fehler besser verwalten können, genau wie ein Zugticket Ihnen bei der Organisation und Verwaltung Ihrer Zugfahrt hilft.
Hier ist ein Beispiel für einen API-Aufruf mithilfe von Versprechen:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
In diesem Code gibt die fetchData-Funktion ein Versprechen zurück. Der Versprechenskonstruktor übernimmt eine Funktion, die zwei Argumente akzeptiert: auflösen und ablehnen. Diese Funktionen werden verwendet, um den Status des Versprechens zu steuern.
Im Promise-Konstruktor simulieren wir einen API-Aufruf mit setTimeout, genau wie im vorherigen Beispiel. Anstatt jedoch eine Rückruffunktion aufzurufen, verwenden wir die Auflösungs- und Ablehnungsfunktionen, um das asynchrone Ergebnis zu verarbeiten.
Wenn ein Fehler auftritt (in diesem Beispiel simulieren wir ihn, indem wir die Fehlervariable überprüfen), rufen wir die Ablehnungsfunktion mit dem Fehler auf und geben an, dass das Versprechen abgelehnt werden soll.
Wenn kein Fehler auftritt, rufen wir die Auflösungsfunktion mit den Daten auf und geben an, dass das Versprechen mit den empfangenen Daten aufgelöst werden soll.
Um die fetchData-Funktion zu verwenden, verketten wir die Methoden .then() und .catch() mit dem Funktionsaufruf. Die Methode .then() wird verwendet, um den aufgelösten Wert des Versprechens zu verarbeiten, während die Methode .catch() verwendet wird, um eventuell auftretende Fehler zu verarbeiten.
Wenn das Versprechen erfolgreich aufgelöst wird, wird die .then()-Methode mit den aufgelösten Daten aufgerufen und wir protokollieren sie mit console.log in der Konsole.
Wenn ein Fehler auftritt und das Versprechen abgelehnt wird, wird die .catch()-Methode mit dem err-Objekt aufgerufen und wir protokollieren es mit console.error in der Konsole.
Die Verwendung von Versprechen bietet im Vergleich zu Rückrufen eine strukturiertere und lesbarere Möglichkeit, asynchrone Vorgänge abzuwickeln. Mit Versprechen können Sie mehrere asynchrone Vorgänge mit .then() miteinander verketten und Fehler mit .catch() zentraler behandeln.
Versprechen wurden bei Rückrufen in mehrfacher Hinsicht verbessert:
Versprechen hatten jedoch immer noch einige Einschränkungen. Die Verkettung mehrerer Versprechen könnte immer noch zu tief verschachteltem Code führen, und die Syntax war nicht so sauber, wie sie sein könnte.
Async/await, eingeführt in ES8 (ECMAScript 2017), basiert auf Versprechen und bietet eine synchroner aussehende Möglichkeit, asynchronen Code zu schreiben.
Mit async/await können Sie asynchronen Code schreiben, der wie synchroner Code aussieht und sich verhält. Es ist, als ob Sie einen persönlichen Assistenten hätten, der für Sie zum Ticketschalter geht. Sie warten einfach darauf, dass Ihr Assistent mit dem Ticket zurückkommt, und sobald er zurückkommt, können Sie Ihre Reise fortsetzen.
Hier ist ein Beispiel für einen API-Aufruf mit async/await:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
In diesem Code haben wir eine asynchrone Funktion namens fetchData, die einen URL-Parameter entgegennimmt, der den API-Endpunkt darstellt. Innerhalb der Funktion verwenden wir einen Try/Catch-Block, um alle Fehler zu behandeln, die während der API-Anfrage auftreten können.
Wir verwenden das Schlüsselwort „await“ vor der fetch-Funktion, um die Ausführung anzuhalten, bis das von fetch zurückgegebene Versprechen aufgelöst ist. Das bedeutet, dass die Funktion wartet, bis die API-Anfrage abgeschlossen ist, bevor sie mit der nächsten Zeile fortfährt.
Sobald die Antwort eingegangen ist, verwenden wir „await Response.json()“, um den Antworttext als JSON zu analysieren. Dies ist ebenfalls ein asynchroner Vorgang, daher verwenden wir „await“, um auf den Abschluss der Analyse zu warten.
Wenn die API-Anfrage und die JSON-Analyse erfolgreich sind, werden die Daten von der fetchData-Funktion zurückgegeben.
Wenn während der API-Anfrage oder der JSON-Analyse ein Fehler auftritt, wird dieser vom Catch-Block abgefangen. Wir protokollieren den Fehler mithilfe von console.error in der Konsole und lösen den Fehler mithilfe von throw err erneut aus, um ihn an den Aufrufer weiterzugeben.
Um die fetchData-Funktion zu verwenden, haben wir eine asynchrone Funktion namens main. Innerhalb von main geben wir die URL des API-Endpunkts an, von dem wir Daten abrufen möchten.
Wir verwenden „await fetchData(url)“, um die fetchData-Funktion aufzurufen und darauf zu warten, dass sie die Daten zurückgibt. Wenn die API-Anfrage erfolgreich ist, protokollieren wir die empfangenen Daten in der Konsole.
Wenn während der API-Anfrage ein Fehler auftritt, wird dieser vom Catch-Block in der Hauptfunktion abgefangen. Wir protokollieren den Fehler mit console.error.
in der KonsoleZuletzt rufen wir die Hauptfunktion auf, um die Ausführung des Programms zu starten.
Wenn Sie diesen Code ausführen, wird mithilfe der Abruffunktion eine asynchrone API-Anfrage an die angegebene URL gesendet. Wenn die Anfrage erfolgreich ist, werden die empfangenen Daten in der Konsole protokolliert. Wenn ein Fehler auftritt, wird dieser ebenfalls abgefangen und protokolliert.
Die Verwendung von async/await mit der Fetch-Funktion bietet eine saubere und lesbare Möglichkeit, asynchrone API-Anfragen zu verarbeiten. Sie können damit asynchronen Code schreiben, der wie synchroner Code aussieht und sich verhält, wodurch er einfacher zu verstehen und zu warten ist.
Zusammenfassend lässt sich sagen, dass die Entwicklung von asynchronem JavaScript, von Callbacks über Promises bis hin zu Async/Await, eine Reise hin zu besser lesbarem, verwaltbarem und wartbarerem asynchronem Code war. Jeder Schritt baut auf dem vorherigen auf, beseitigt die Einschränkungen und verbessert die Entwicklererfahrung.
Async/await ist heute weit verbreitet und hat sich zur bevorzugten Methode zur Verarbeitung asynchroner Vorgänge in JavaScript entwickelt. Es ermöglicht Entwicklern, asynchronen Code zu schreiben, der sauber, prägnant und leicht verständlich ist, was es zu einem wertvollen Werkzeug im Werkzeugkasten jedes JavaScript-Entwicklers macht.
Das obige ist der detaillierte Inhalt vonAsynchrones JavaScript – Der Weg von Callbacks zu Async Await. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!