Asynchron dient dazu, die CPU-Auslastung zu erhöhen und sie ständig beschäftigt zu halten.
Einige Vorgänge (der typischste ist E/A) erfordern keine CPU-Beteiligung und sind sehr zeitaufwändig. Wenn Asynchronität nicht verwendet wird, entsteht ein Blockierungszustand, der dazu führt, dass die CPU im Leerlauf ist und die Seite einfriert.
Wenn eine E/A-Operation in einer asynchronen Umgebung auftritt, legt die CPU die E/A-Arbeit beiseite (zu diesem Zeitpunkt wird die E/A von anderen Controllern übernommen und es werden weiterhin Daten übertragen) und verarbeitet dann die nächste Aufgabe Warten Sie, bis der E/A-Vorgang abgeschlossen ist. Benachrichtigen Sie die CPU (Rückruf ist eine Benachrichtigungsmethode), damit sie wieder funktioniert.
Der Kerninhalt von „JavaScript Asynchronous and Callback“ besteht darin, dass die spezifische Endzeit der asynchronen Arbeit ungewiss ist. Um die nachfolgende Verarbeitung nach Abschluss der asynchronen Arbeit genau durchzuführen, muss ein Rückruf an die asynchrone Funktion übergeben werden Nachdem Sie Ihre Arbeit abgeschlossen haben, fahren Sie mit den folgenden Aufgaben fort.
Obwohl Callbacks sehr einfach asynchron zu implementieren sein können, können sie aufgrund der mehrfachen Verschachtelung eine Callback-Hölle bilden. Um die Callback-Hölle zu vermeiden, müssen Sie die verschachtelte Programmierung entschachteln und in eine lineare Programmierung ändern.
Promise
ist die beste Lösung, um die Callback-Hölle in JavaScript
zu bewältigen.
Promise
kann als „Versprechen“ übersetzt werden. Wir können asynchrone Arbeit kapseln und sie „ Promise
“ nennen, das heißt, ein Versprechen abgeben und versprechen, nach dem Ende der asynchronen Arbeit ein klares Signal zu geben!
Promise
Syntax:
let Promise = new Promise(function(resolve,reject){ // Asynchrone Arbeit})
Durch die obige Syntax können wir asynchrone Arbeit in ein Promise
kapseln. Die beim Erstellen Promise
übergebene Funktion ist die Methode zur Verarbeitung asynchroner Arbeit, auch executor
genannt.
resolve
und reject
sind Rückruffunktionen, die von JavaScript
selbst bereitgestellt werden. Sie können aufgerufen werden, wenn executor
die Aufgabe abschließt:
resolve(result)
– wenn sie erfolgreich abgeschlossen wird, wird result
„error
reject(error)
zurückgegeben – wenn die Ausführung fehlschlägt Es wird error
generiert.executor
wird automatisch ausgeführt, sobald Promise
erstellt wurde, und sein Ausführungsstatus ändert den Status der internen Eigenschaften Promise
:
state
– zunächst pending
, dann nach dem Aufruf von resolve
in fulfilled
“ umgewandelt oder wird rejected
wenn reject
aufgerufen wird;result
– Es undefined
und wird dann zum value
, nachdem resolve(value)
aufgerufen wurde, oder wird zu error
, nachdem reject
aufgerufen wurde.fs.readFile
eine asynchrone Funktion Wir können es an executor
übergeben, um Dateilesevorgänge in der Datei durchzuführen, wodurch asynchrone Arbeit gekapselt wird.
Der folgende Code kapselt die Funktion fs.readFile
und verwendet resolve(data)
für die Verarbeitung erfolgreicher Ergebnisse und reject(err)
für die Verarbeitung fehlgeschlagener Ergebnisse.
Der Code lautet wie folgt:
let Promise = new Promise((resolve,ject) => { fs.readFile('1.txt', (err, data) => { console.log('Read 1.txt') wenn (fehler) ablehnen (fehler) auflösen(Daten) })})
Wenn wir diesen Code ausführen, werden die Worte „Read 1.txt“ ausgegeben, was beweist, dass der Dateilesevorgang unmittelbar nach der Erstellung Promise
ausgeführt wird.
Promise
kapselt normalerweise asynchronen Code intern, es kapselt jedoch nicht nur asynchronen Code.
Der obige Promise
-Fall kapselt den Dateilesevorgang. Die Datei wird unmittelbar nach Abschluss der Erstellung gelesen. Wenn Sie das Ergebnis der Promise
-Ausführung erhalten möchten, müssen Sie drei Methoden verwenden then
, catch
und finally
.
Die then
Methode von Promise
kann verwendet werden, um die Arbeit zu erledigen, nachdem Promise
Ausführung abgeschlossen ist. Sie erhält zwei Rückrufparameter. Die Syntax lautet wie folgt:
Promise.then(function(result),function(error))
result
ist der von resolve
empfangene Wert;error
ist der von reject
empfangene
Parameter Versprechen = neues Versprechen((lösen, ablehnen) => { fs.readFile('1.txt', (err, data) => { console.log('Read 1.txt') wenn (fehler) ablehnen (fehler) auflösen(Daten) })})promise.then( (Daten) => { console.log('Erfolgreich ausgeführt, das Ergebnis ist' + data.toString()) }, (irrt) => { console.log('Ausführung fehlgeschlagen, Fehler ist' + err.message) })
Wenn das Lesen der Datei erfolgreich ausgeführt wurde, wird die erste Funktion aufgerufen:
PS E:CodeNodedemos 3-callback> node .index.js Lesen Sie 1.txt Bei erfolgreicher Ausführung ist das Ergebnis 1
gelöscht 1.txt
. Wenn die Ausführung fehlschlägt, wird die zweite Funktion aufgerufen:
PS E:CodeNodedemos 3-callback> node .index.js Lesen Sie 1.txt Die Ausführung ist mit dem Fehler ENOENT: no such file or directory, open 'E:CodeNodedemos 3-callback1.txt' fehlgeschlagen.
Wenn wir uns nur auf das Ergebnis einer erfolgreichen Ausführung konzentrieren, können wir nur eines übergeben Rückruffunktion:
Promise .then((data)=>{ console.log('Erfolgreich ausgeführt, das Ergebnis ist' + data.toString())})
An diesem Punkt haben wir einen asynchronen Lesevorgang der Datei implementiert.
Wenn wir uns nur auf das Fehlerergebnis konzentrieren, können wir null
an den ersten then
übergeben: promise.then(null,(err)=>{...})
.
Oder verwenden Sie einen eleganteren Weg: promise.catch((err)=>{...})
let Promise = new Promise((resolve,ject) => { fs.readFile('1.txt', (err, data) => { console.log('Read 1.txt') wenn (fehler) ablehnen (fehler) auflösen(Daten) })})promise.catch((err)=>{ console.log(err.message)})
.catch((err)=>{...})
und then(null,(err)=>{...})
haben genau den gleichen Effekt.
.finally
ist eine Funktion, die unabhängig vom Ergebnis von promise
ausgeführt wird. Sie hat den gleichen Zweck wie finally
in try...catch...
-Syntax und kann Vorgänge verarbeiten, die nichts mit dem Ergebnis zu tun haben.
Zum Beispiel:
new Promise((resolve,reject)=>{ //etwas...}).finally(()=>{console.log('Unabhängig vom Ergebnis ausführen')}).then(result=>{...}, err=>{...} )Der „final“-Rückruf hat keine Parameter
fs.readFile()
liest 10 Dateien nacheinander und gibt den Inhalt aus der zehn Dateien nacheinander.
Da fs.readFile()
selbst asynchron ist, müssen wir die Rückrufverschachtelung verwenden. Der Code lautet wie folgt:
fs.readFile('1.txt', (err, data) => { console.log(data.toString()) //1 fs.readFile('2.txt', (err, data) => { console.log(data.toString()) fs.readFile('3.txt', (err, data) => { console.log(data.toString()) fs.readFile('4.txt', (err, data) => { console.log(data.toString()) fs.readFile('5.txt', (err, data) => { console.log(data.toString()) fs.readFile('6.txt', (err, data) => { console.log(data.toString()) fs.readFile('7.txt', (err, data) => { console.log(data.toString()) fs.readFile('8.txt', (err, data) => { console.log(data.toString()) fs.readFile('9.txt', (err, data) => { console.log(data.toString()) fs.readFile('10.txt', (err, data) => { console.log(data.toString()) // ==> Hell's Gate}) }) }) }) }) }) }) }) })})
Obwohl der obige Code die Aufgabe erledigen kann, wird die Codeebene mit zunehmender Aufrufverschachtelung tiefer und die Wartungsschwierigkeit nimmt zu, insbesondere wenn wir echten Code verwenden, der möglicherweise viele Schleifen und bedingte Anweisungen enthält einfaches console.log(...)
im Beispiel.
Was passiert, wenn wir keine Rückrufe verwenden und fs.readFile()
direkt nacheinander gemäß dem folgenden Code aufrufen?
//Hinweis: Dies ist die falsche Schreibweise fs.readFile('1.txt', (err, data) => { console.log(data.toString())})fs.readFile('2.txt', (err, data) => { console.log(data.toString())})fs.readFile('3.txt', (err, data) => { console.log(data.toString())})fs.readFile('4.txt', (err, data) => { console.log(data.toString())})fs.readFile('5.txt', (err, data) => { console.log(data.toString())})fs.readFile('6.txt', (err, data) => { console.log(data.toString())})fs.readFile('7.txt', (err, data) => { console.log(data.toString())})fs.readFile('8.txt', (err, data) => { console.log(data.toString())})fs.readFile('9.txt', (err, data) => { console.log(data.toString())})fs.readFile('10.txt', (err, data) => { console.log(data.toString())})
Das Folgende sind die Ergebnisse meines Tests (die Ergebnisse jeder Ausführung sind unterschiedlich):
PS E:CodeNodedemos 3-callback> node .index DerDer Grund dafür, dass
js12346957108
dieses nicht-sequentielle Ergebnis erzeugt, ist asynchron . Es kann keine Multithread-Parallelität erreicht werden. Asynchronität kann in einem einzelnen Thread erreicht werden.
Der Grund, warum dieser Fehlerfall hier verwendet wird, besteht darin, das Konzept der Asynchronität hervorzuheben. Wenn Sie nicht verstehen, warum dieses Ergebnis auftritt, müssen Sie zurückgehen und die Lektion nachholen!
Die Idee, Promise
zum Lösen des asynchronen sequentiellen Dateilesens zu verwenden:
promise1
zu lesen, und verwenden Sie resolve
, um das Ergebnis zurückzugeben.promise2
,promise1.then
das Ergebnis des Dateilesens zu empfangen und auszugebenPromise1.then
promise1.then
Und zurück,promise2.then
aufzurufen, um das Leseergebnis zu empfangen und auszugebenpromise3
Objekt in promise2.then
und kehren Sie zurück, umpromise3.then
aufzurufen, um das Leseergebnis zu empfangen undDer Code lautet wie folgt:
let Promise1 = new Promise( (auflösen, ablehnen) => { fs.readFile('1.txt', (err, data) => { wenn (fehler) ablehnen (fehler) auflösen(Daten) })})let Promise2 = Promise1.then( Daten => { console.log(data.toString()) neues Versprechen zurückgeben((auflösen, ablehnen) => { fs.readFile('2.txt', (err, data) => { wenn (fehler) ablehnen (fehler) auflösen(Daten) }) }) })lass versprechen3 = versprechen2.then( Daten => { console.log(data.toString()) neues Versprechen zurückgeben((auflösen, ablehnen) => { fs.readFile('3.txt', (err, data) => { wenn (fehler) ablehnen (fehler) auflösen(Daten) }) }) })lass versprechen4 = versprechen3.then( Daten => { console.log(data.toString()) //..... })... ...
Auf diese Weise schreiben wir die ursprüngliche verschachtelte Callback-Hölle in einen linearen Modus.
Es gibt jedoch immer noch ein Problem mit dem Code. Obwohl der Code in Bezug auf die Verwaltung schöner geworden ist, nimmt die Länge des Codes erheblich zu.
Der obige Code ist zu lang. Wir können die Codemenge durch zwei Schritte reduzieren:
promise
weglassen und die .then
Datei verknüpfenCode wie folgt:
function myReadFile (path) { neues Versprechen zurückgeben((auflösen, ablehnen) => { fs.readFile(path, (err, data) => { wenn (fehler) ablehnen (fehler) console.log(data.toString()) lösen() }) })}myReadFile('1.txt') .then(data => { return myReadFile('2.txt') }) .then(data => { return myReadFile('3.txt') }) .then(data => { return myReadFile('4.txt') }) .then(data => { return myReadFile('5.txt') }) .then(data => { return myReadFile('6.txt') }) .then(data => { return myReadFile('7.txt') }) .then(data => { return myReadFile('8.txt') }) .then(data => { return myReadFile('9.txt') }) .then(data => { return myReadFile('10.txt') })
Da die myReadFile
-Methode ein neues Promise
zurückgibt, können wir die .then
-Methode direkt ausführen. Diese Programmiermethode wird Kettenprogrammierung genannt.
Das Ergebnis der Codeausführung lautet wie folgt:
PS E:CodeNodedemos 3-callback> node .index.js12345678910
Damit ist der asynchrone und sequentielle Dateilesevorgang abgeschlossen.
Hinweis: In der
.then
Methode jedes Schritts muss ein neuesPromise
Objekt zurückgegeben werden, andernfalls wird das vorherige altePromise
empfangen.Dies liegt daran, dass jede
then
-Methode ihrPromise
weiterhin nach unten weitergibt.