Bevor wir den Inhalt dieses Artikels studieren, müssen wir zunächst das Konzept von asynchron verstehen. Zunächst muss betont werden, dass es einen wesentlichen Unterschied zwischen asynchron und parallel gibt.
Parallelität bezieht sich im Allgemeinen auf paralleles Rechnen, was bedeutet, dass mehrere Anweisungen gleichzeitig ausgeführt werden können. Diese Anweisungen können auf mehreren Kernen derselben CPU
oder auf mehreren CPU
oder auf mehreren physischen Hosts oder sogar mehreren Netzwerken ausgeführt werden.
Unter Synchronisierung versteht man im Allgemeinen die Ausführung von Aufgaben in einer vorgegebenen Reihenfolge. Erst wenn die vorherige Aufgabe abgeschlossen ist, wird die nächste Aufgabe ausgeführt.
Asynchron bedeutet, dass CPU
die aktuelle Aufgabe vorübergehend beiseite legt, zuerst die nächste Aufgabe verarbeitet und dann zur vorherigen Aufgabe zurückkehrt , um die Ausführung fortzusetzen, nachdem sie die Rückrufbenachrichtigung der vorherigen Aufgabe erhalten hat Zweiter Thread mitmachen .
Vielleicht ist es intuitiver, Parallelität, Synchronisation und Asynchronität in Form von Bildern zu erklären. Angenommen, es gibt zwei Aufgaben A und B, die verarbeitet werden müssen. Die parallelen, synchronen und asynchronen Verarbeitungsmethoden übernehmen die in der Abbildung gezeigten Ausführungsmethoden folgende Abbildung:
JavaScript
bietet uns viele asynchrone Funktionen, mit denen wir asynchrone Aufgaben bequem ausführen können. Das heißt, wir beginnen jetzt mit der Ausführung einer Aufgabe (Funktion), die Aufgabe wird jedoch später und zu einem bestimmten Zeitpunkt abgeschlossen ist nicht sicher.
Beispielsweise ist die Funktion setTimeout
eine sehr typische asynchrone Funktion. Darüber hinaus sind fs.readFile
und fs.writeFile
ebenfalls asynchrone Funktionen.
Wir können einen asynchronen Aufgabenfall selbst definieren, z. B. das Anpassen einer Dateikopierfunktion copyFile(from,to)
:
const fs = require('fs')function copyFile(from, to) { fs.readFile(from, (err, data) => { if (irre) { console.log(err.message) zurückkehren } fs.writeFile(to, data, (err) => { if (irre) { console.log(err.message) zurückkehren } console.log('Kopieren abgeschlossen') }) })}
Die Funktion copyFile
liest zunächst die Dateidaten aus dem Parameter from
und schreibt dann die Daten in die Datei, auf die der Parameter to
zeigt.
Wir können copyFile
wie folgt aufrufen:
copyFile('./from.txt','./to.txt')//Kopieren Sie die Datei.
Wenn nach copyFile(...)
zu diesem Zeitpunkt anderer Code vorhanden ist, wird das Programm dies nicht tun wait Die Ausführung von copyFile
endet, aber es wird direkt nach unten ausgeführt. Dem Programm ist es egal, wann die Dateikopieraufgabe endet.
copyFile('./from.txt','./to.txt')//Der folgende Code wartet nicht auf das Ende der Ausführung des obigen Codes ...
An diesem Punkt scheint alles normal zu sein, aber wenn we Was passiert, wenn Sie nach der Funktion copyFile(...)
direkt auf den Inhalt der Datei ./to.txt
zugreifen?
Dadurch wird der kopierte Inhalt nicht gelesen, einfach so:
copyFile('./from.txt','./to.txt')fs.readFile('./to.txt',(err,data)= >{ ...})
Wenn die ./to.txt
vor der Ausführung des Programms nicht erstellt wurde, erhalten Sie die folgende Fehlermeldung:
PS E:CodeNodedemos 3-callback> node .index.js fertig Kopieren fertig PS E:CodeNodedemos 3-callback> node .index.js Fehler: ENOENT: keine solche Datei oder kein solches Verzeichnis, öffnen Sie 'E:CodeNodedemos 3-callbackto.txt'Kopieren abgeschlossen
Auch wenn ./to.txt
vorhanden ist, kann der kopierte Inhalt nicht gelesen werden.
Der Grund für dieses Phänomen ist: Nachdem das Programm copyFile(...)
copyFile(...)
hat, wartet es nicht auf den Abschluss des Kopiervorgangs, sondern führt ihn direkt nach unten aus, wodurch die Datei aufgerufen wird Es erscheint der Fehler ./to.txt
existiert nicht“ oder der Fehler „Der Dateiinhalt ist leer“ (wenn die Datei im Voraus erstellt wurde).
Die spezifische Ausführungsendzeit der asynchronen Rückruffunktion kann nicht bestimmt werden. Beispielsweise hängt die Ausführungsendzeit readFile(from,to)
höchstwahrscheinlich von der Größe der Datei from
.
Die Frage ist also, wie können wir das Ende der Ausführung copyFile
genau lokalisieren und den Inhalt der to
Datei lesen?
Dies erfordert die Verwendung einer Callback-Funktion. Wir können die copyFile
-Funktion wie folgt ändern:
function copyFile(from, to, callback) { fs.readFile(from, (err, data) => { if (irre) { console.log(err.message) zurückkehren } fs.writeFile(to, data, (err) => { if (irre) { console.log(err.message) zurückkehren } console.log('Kopieren abgeschlossen') callback()//Callback-Funktion wird aufgerufen, wenn der Kopiervorgang abgeschlossen ist}) })}
Wenn wir auf diese Weise einige Vorgänge unmittelbar nach Abschluss der Dateikopie ausführen müssen, können wir diese Vorgänge in die Rückruffunktion schreiben:
function copyFile(from, to, callback) { fs.readFile(from, (err, data) => { if (irre) { console.log(err.message) zurückkehren } fs.writeFile(to, data, (err) => { if (irre) { console.log(err.message) zurückkehren } console.log('Kopieren abgeschlossen') callback()//Callback-Funktion wird aufgerufen, wenn der Kopiervorgang abgeschlossen ist}) })}copyFile('./from.txt', './to.txt', function () { //Eine Rückruffunktion übergeben, den Inhalt der Datei „to.txt“ lesen und ausgeben fs.readFile('./to.txt', (err, data) => { if (irre) { console.log(err.message) zurückkehren } console.log(data.toString()) })})
Wenn Sie die Datei ./from.txt
vorbereitet haben, kann der obige Code direkt ausgeführt werden:
PS E:CodeNodedemos 3-callback> node .index.js Kopieren fertig Treten Sie der Community „Xianzong“ bei und kultivieren Sie mit mir die Unsterblichkeit. Community-Adresse: http://t.csdn.cn/EKf1h
Diese Programmiermethode wird als „Callback-basierter“ asynchroner Programmierstil bezeichnet Wird zum Aufrufen verwendet, nachdem die Aufgabe beendet ist.
Dieser Stil ist in der JavaScript
-Programmierung üblich. Beispielsweise sind die Dateilesefunktionen fs.readFile
und fs.writeFile
allesamt asynchrone Funktionen.
Die Callback-Funktion kann nachfolgende Angelegenheiten nach Abschluss der asynchronen Arbeit genau verarbeiten. Wenn wir mehrere asynchrone Vorgänge nacheinander ausführen müssen, müssen wir die Callback-Funktion verschachteln.
Fallbeispiel:
Code-Implementierung zum Lesen von Datei A und Datei B nacheinander:
fs.readFile('./A.txt', (err, data) => { if (irre) { console.log(err.message) zurückkehren } console.log('Datei A lesen: ' + data.toString()) fs.readFile('./B.txt', (err, data) => { if (irre) { console.log(err.message) zurückkehren } console.log("Datei B lesen: " + data.toString()) })})
Ausführungseffekt:
PS E:CodeNodedemos 3-callback> node .index.js Das Lesen von Datei A: Immortal Sect ist unendlich gut, aber es fehlt jemand. Wenn Sie Immortal Sect beitreten möchten, müssen Sie den Link http://t.csdn.cn/H1faI haben,
den Sie lesen können Die Datei wird sofort nach A gelesen.
Was ist, wenn wir Datei C nach Datei B weiterlesen möchten? Dies erfordert weiterhin die Verschachtelung von Rückrufen:
fs.readFile('./A.txt', (err, data) => {//First callback if (err) { console.log(err.message) zurückkehren } console.log('Datei A lesen: ' + data.toString()) fs.readFile('./B.txt', (err, data) => {//Zweiter Rückruf if (err) { console.log(err.message) zurückkehren } console.log("Datei B lesen: " + data.toString()) fs.readFile('./C.txt',(err,data)=>{//Der dritte Rückruf... }) })})
Mit anderen Worten: Wenn wir mehrere asynchrone Vorgänge nacheinander ausführen möchten, benötigen wir mehrere Ebenen verschachtelter Rückrufe. Dies ist effektiv, wenn die Anzahl der Ebenen gering ist, aber wenn es zu viele Verschachtelungszeiten gibt, treten einige Probleme auf auftreten.
Rückrufkonventionen
Tatsächlich ist der Stil der Rückruffunktionen in fs.readFile
keine Ausnahme, sondern eine gängige Konvention in JavaScript
. Wir werden in Zukunft eine große Anzahl von Rückruffunktionen anpassen, und wir müssen uns an diese Konvention halten und gute Codierungsgewohnheiten entwickeln.
Die Konvention lautet:
callback
ist für Fehler reserviert. Sobald ein Fehler auftritt, wird callback(err)
aufgerufen.callback(null, result1, result2,...)
aufgerufen.Basierend auf der oben genannten Konvention hat eine Rückruffunktion zwei Funktionen: Fehlerbehandlung und Ergebnisempfang. Beispielsweise folgt die Rückruffunktion von fs.readFile('...',(err,data)=>{})
dieser Konvention.
Wenn wir nicht näher darauf eingehen, scheint die auf Callbacks basierende asynchrone Methodenverarbeitung eine ziemlich perfekte Möglichkeit zu sein, damit umzugehen. Das Problem besteht darin, dass der Code folgendermaßen aussieht, wenn wir ein asynchrones Verhalten nach dem anderen haben:
fs.readFile('./a.txt',(err,data)=>{ if(err){ console.log(err.message) zurückkehren } //Ergebnisvorgang lesen fs.readFile('./b.txt',(err,data)=>{ if(err){ console.log(err.message) zurückkehren } //Ergebnisoperation lesen fs.readFile('./c.txt',(err,data)=>{ if(err){ console.log(err.message) zurückkehren } //Ergebnisoperation lesen fs.readFile('./d.txt',(err,data)=>{ if(err){ console.log(err.message) zurückkehren } ... }) }) })})
Der Ausführungsinhalt des obigen Codes lautet:
Mit zunehmender Anzahl der Aufrufe wird die Verschachtelungsebene des Codes immer tiefer, einschließlich immer mehr bedingter Anweisungen, was zu verwirrendem Code führt, der ständig nach rechts eingerückt wird, was das Lesen und Schreiben erschwert pflegen.
Wir nennen dieses Phänomen des kontinuierlichen Wachstums nach rechts (Einrückung nach rechts) „ Callback-Hölle “ oder „ Pyramide des Untergangs “!
fs.readFile('a.txt',(err,data)=>{ fs.readFile('b.txt',(err,data)=>{ fs.readFile('c.txt',(err,data)=>{ fs.readFile('d.txt',(err,data)=>{ fs.readFile('e.txt',(err,data)=>{ fs.readFile('f.txt',(err,data)=>{ fs.readFile('g.txt',(err,data)=>{ fs.readFile('h.txt',(err,data)=>{ ... /* Tor zur Hölle ===> */ }) }) }) }) }) }) })})
Obwohl der obige Code recht regelmäßig aussieht, handelt es sich beispielsweise nur um eine Idealsituation. Normalerweise gibt es in der Geschäftslogik eine große Anzahl von bedingten Anweisungen, Datenverarbeitungsoperationen und anderen Codes, die die aktuelle schöne Reihenfolge stören Die Codeänderung ist schwierig aufrechtzuerhalten.
Glücklicherweise bietet uns JavaScript
mehrere Lösungen, und Promise
ist die beste Lösung.