VorwortBei der asynchronen Programmierung spielt Promise eine wichtige Rolle und ist sinnvoller und leistungsfähiger als herkömmliche Lösungen (Rückruffunktionen und Ereignisse). Einige von Ihnen stellen sich möglicherweise diese Frage: Wir schreiben das Jahr 2020, warum sprechen wir immer noch über Promise? Tatsächlich scheinen einige Freunde diesen „alten Freund“, mit dem sie fast täglich zu tun haben, zu verstehen, aber wenn sie tiefer graben, haben sie möglicherweise viele Fragen. Dieser Artikel wird Ihnen helfen, diesen vertrauten Fremden – Promise – besser zu verstehen. Grundlegende VerwendungGrammatikneues Promise(Funktion(auflösen, ablehnen) {...} /* Ausführender */)
Es ist erwähnenswert, dass Promise zur Verwaltung asynchroner Programmierung verwendet wird. Es ist selbst nicht asynchron. Wenn ein neues Promise erstellt wird, wird die Executor-Funktion sofort ausgeführt. Normalerweise verarbeiten wir jedoch einen asynchronen Vorgang in der Executor-Funktion. Im folgenden Code wird beispielsweise am Anfang eine 2 ausgedruckt. lass p1 = neues Versprechen(()=>{ setzeTimeout(()=>{ console.log(1) },1000) console.log(2) }) console.log(3) // 2 3 1 Promise verwendet die Technologie der verzögerten Bindung von Rückruffunktionen. Wenn die Resolve-Funktion ausgeführt wird, ist die Rückruffunktion noch nicht gebunden, sodass die Ausführung der Rückruffunktion nur verschoben werden kann. Was bedeutet das genau? Schauen wir uns das folgende Beispiel an: let p1 = neues Versprechen((lösen,ablehnen)=>{ konsole.log(1); lösen('Boot in den Wellen') console.log(2) }) // dann: Legen Sie die Methode für die Nachbearbeitung bei Erfolg oder Misserfolg fest p1.then(result=>{ //p1 verzögerte Bindungsrückruffunktion console.log('Erfolg'+Ergebnis) },Grund=>{ console.log('fehlgeschlagen' + Grund) }) console.log(3) // 1 // 2 // 3 // Erfolg in den Wellen Wenn ein neues Promise erstellt wird, wird zuerst die Executor-Funktion ausgeführt und 1 und 2 werden gedruckt. Wenn das Promise aufgelöst ist, wird die Mikrotask ausgelöst oder die synchrone Aufgabe fortgesetzt. Wenn p1.then ausgeführt wird, werden zwei Funktionen gespeichert (diese beiden Funktionen wurden zu diesem Zeitpunkt noch nicht ausgeführt) und dann 3 gedruckt. Zu diesem Zeitpunkt ist die Synchronisierungsaufgabe abgeschlossen und schließlich wird die Mikrotask ausgeführt, wodurch die erfolgreiche Methode in .then ausgeführt wird. FehlerbehandlungDer Fehler des Promise-Objekts hat einen „sprudelnden“ Charakter und wird zurückgegeben, bis er von der Funktion „onReject“ verarbeitet oder von der Catch-Anweisung erfasst wird. Mit dieser „Bubbling“-Funktion ist es nicht erforderlich, Ausnahmen in jedem Promise-Objekt separat abzufangen. Um auf ein „then“ zu stoßen, führen Sie die Erfolgs- oder Fehlermethode aus. Wenn diese Methode jedoch im aktuellen „then“ nicht definiert ist, wird sie auf die nächste entsprechende Funktion verschoben. Funktion Executor (auflösen, ablehnen) { let rand = Math.random() console.log(1) console.log(rand) wenn (rand > 0,5) { lösen() } anders { ablehnen() } } var p0 = neues Versprechen (Vollstrecker) var p1 = p0.then((Wert) => { console.log('erfolgreich-1') gib neues Promise(Vollstrecker) zurück }) var p2 = p1.then((Wert) => { console.log('erfolgreich-2') gib neues Promise(Vollstrecker) zurück }) p2.catch((Fehler) => { console.log('Fehler', Fehler) }) console.log(2) Dieser Code hat drei Promise-Objekte: p0~p2. Unabhängig davon, welches Objekt eine Ausnahme auslöst, kann das letzte Objekt p2.catch verwendet werden, um die Ausnahme abzufangen. Auf diese Weise können Fehler aller Promise-Objekte zur Verarbeitung in einer Funktion zusammengeführt werden, wodurch das Problem gelöst wird, dass jede Aufgabe Ausnahmen separat behandeln muss. Auf diese Weise vermeiden wir verschachtelte Aufrufe und häufige Fehlerbehandlungen, wodurch der von uns geschriebene Code eleganter wird und mehr dem linearen Denken des Menschen entspricht. Promise-Chain-AufrufWir alle wissen, dass wir mehrere Promises miteinander verketten können, um eine Reihe verschiedener Schritte darzustellen. Der Schlüssel zu diesem Ansatz sind die folgenden zwei inhärenten Verhaltensweisen von Promises:
Lassen Sie uns zunächst anhand des folgenden Beispiels die Bedeutung dieses Satzes erklären und dann den Ausführungsprozess des Kettenaufrufs im Detail vorstellen. let p1 = neues Versprechen((lösen, ablehnen) => { resolve(100) // bestimmt die nächste erfolgreiche Methode im then, die ausgeführt werden soll}) // Mit p1 verbinden sei p2=p1.dann(Ergebnis=>{ console.log('Erfolg 1 '+Ergebnis) Rückgabewert Promise.reject(1) //Gibt eine neue Promise-Instanz zurück, die feststellt, dass die aktuelle Instanz ein Fehler ist, sodass die nächste Methode ausgeführt wird}, reason=>{ console.log('Fehler 1 '+Grund) Rückgabe 200 }) // Mit p2 verbinden sei p3=p2.dann(Ergebnis=>{ console.log('Erfolg 2' + Ergebnis) },Grund=>{ console.log('Fehler 2 '+Grund) }) // Erfolg 1 100 // Fehler 2 1 Wir erfüllen das durch den ersten Aufruf erstellte und zurückgegebene Versprechen p2, indem wir dann Promise.reject(1) zurückgeben. Der dann ausgeführte Aufruf von p2 erhält den Erfüllungswert aus der Anweisung return Promise.reject(1). Natürlich erstellt p2.then ein weiteres neues Promise, das in der Variable p3 gespeichert werden kann. Der Erfolg oder Misserfolg der von new Promise generierten Instanz hängt davon ab, ob die Auflösung oder Ablehnung bei der Ausführung der Executor-Funktion ausgeführt wird oder ob während der Ausführung der Executor-Funktion ein abnormaler Fehler auftritt. In beiden Fällen wird der Instanzstatus auf „Fehlschlag“ geändert. Der Status der neuen Instanz, den p2 bei der Ausführung zurückgibt, bestimmt, welche Methode im nächsten ausgeführt wird. Es gibt mehrere Situationen:
Schauen wir uns ein weiteres Beispiel an neues Versprechen(resolve=>{ resolve(a) // Fehler // Bei der Ausführung dieser Executor-Funktion tritt ein Ausnahmefehler auf, der dazu führt, dass die nächste, dann fehlerhafte Methode ausgeführt wird}).then(result=>{ console.log(`Erfolg: ${result}`) Ergebnis zurückgeben*10 },Grund=>{ console.log(`Fehlgeschlagen: ${reason}`) // Bei der Ausführung dieses Satzes tritt keine Ausnahme auf oder es wird eine fehlgeschlagene Promise-Instanz zurückgegeben, sodass die nächste, dann erfolgreiche Methode ausgeführt wird // Hier gibt es keine Rückgabe und am Ende wird undefined zurückgegeben }).dann(Ergebnis=>{ console.log(`Erfolg: ${result}`) },Grund=>{ console.log(`Fehlgeschlagen: ${reason}`) }) // Fehler: Referenzfehler: a ist nicht definiert // Erfolg: undefiniert asynchron und wartenAus den obigen Beispielen können wir ersehen, dass die Verwendung von Promise das Problem der Callback-Hölle zwar sehr gut lösen kann, diese Methode jedoch voller then()-Methoden von Promise ist. Wenn der Verarbeitungsfluss komplizierter ist, ist der gesamte Code voller then, die Semantik ist nicht offensichtlich und der Code kann den Ausführungsfluss nicht gut darstellen. Die Implementierung von async/await, einer neuen asynchronen Programmiermethode, die in ES7 hinzugefügt wurde, basiert auf Promise. Einfach ausgedrückt gibt die async-Funktion ein Promise-Objekt zurück, das die Syntax des Generators darstellt. Viele Leute denken, dass async/await die ultimative Lösung für asynchrone Vorgänge ist:
Es gibt jedoch auch einige Nachteile, da await asynchronen Code in synchronen Code umwandelt. Wenn mehrere asynchrone Codes keine Abhängigkeiten haben, aber await verwenden, wird die Leistung reduziert. asynchroner Funktionstest () { // Wenn der folgende Code keine Abhängigkeiten hat, können Sie Promise.all verwenden. // Wenn Abhängigkeiten vorhanden sind, ist dies tatsächlich ein Beispiel für die Lösung von Callback Hell await fetch(url1). warte auf Abruf (URL2) warte auf Abruf (URL3) } Können Sie anhand des folgenden Codes erkennen, was gedruckt wird? lass p1 = Promise.resolve(1) lass p2 = neues Versprechen(auflösen => { setzeTimeout(() => { Entschlossenheit(2) }, 1000) }) asynchrone Funktion fn() { console.log(1) // Wenn der Code bis zu dieser Zeile ausgeführt wird (diese Zeile an den Anfang setzen), wird eine asynchrone Mikrotask erstellt // Auf die Versprechen warten, das Ergebnis zurückzugeben, und der Code unter await wird auch in der Task-Warteschlange aufgeführt let result1 = await p2 console.log(3) lass result2 = warte auf p1 console.log(4) } fn() console.log(2) // 1 2 3 4 Wenn die auf der rechten Seite von „await“ ausgedrückte Logik ein Versprechen ist, wartet „await“ auf das Rückgabeergebnis des Versprechens. Erst wenn der zurückgegebene Status aufgelöst ist, wird das Ergebnis zurückgegeben. Wenn das Versprechen fehlgeschlagen ist, erhält „await“ sein Rückgabeergebnis nicht und der Code unter „await“ wird nicht weiter ausgeführt. sei p1 = Promise.reject(100) asynchrone Funktion fn1() { lass Ergebnis = warte auf p1 console.log(1) //Diese Codezeile wird nicht ausgeführt} Schauen wir uns eine komplexere Frage an: console.log(1) setTimeout(()=>{console.log(2)},1000) asynchrone Funktion fn(){ console.log(3) setTimeout(()=>{console.log(4)},20) returniere Promise.reject() } asynchrone Funktion run() { console.log(5) warte auf fn() konsole.log(6) } laufen() //Die Ausführung dauert etwa 150 ms for(let i=0;i<90000000;i++){} setzeTimeout(()=>{ konsole.log(7) neues Versprechen(resolve=>{ konsole.log(8) lösen() }).then(()=>{console.log(9)}) },0) konsole.log(10) // 1 5 3 10 4 7 8 9 2 Bevor Sie diese Frage beantworten, müssen die Leser Folgendes verstehen:
Als nächstes analysieren wir Schritt für Schritt:
Häufig verwendete Methoden1. Versprechen.lösen() Die Methode Promise.resolve(value) gibt ein Promise-Objekt zurück, das mit dem angegebenen Wert aufgelöst wird. Versprechen.lösen('foo') // Entspricht neuem Promise(resolve => resolve('foo')) Die Parameter der Methode Promise.resolve sind in vier Fälle unterteilt. (1) Der Parameter ist eine Promise-Instanz Wenn der Parameter eine Promise-Instanz ist, gibt Promise.resolve die Instanz unverändert und ohne Änderungen zurück. const p1 = neues Versprechen (Funktion (auflösen, ablehnen) { setTimeout(() => ablehnen(neuer Fehler('fehlgeschlagen')), 3000) }) const p2 = neues Versprechen (Funktion (auflösen, ablehnen) { setTimeout(() => auflösen(p1), 1000) }) Teil 2 .then(Ergebnis => console.log(Ergebnis)) .catch(Fehler => console.log(Fehler)) // Fehler: fehlgeschlagen Im obigen Code ist p1 ein Versprechen, das nach 3 Sekunden abgelehnt wird. Der Status von p2 ändert sich nach 1 Sekunde und die Resolve-Methode gibt p1 zurück. Da p2 ein anderes Promise zurückgibt, wird der Zustand von p2 selbst ungültig und der Zustand von p2 wird durch den Zustand von p1 bestimmt. Daher zielen alle nachfolgenden „then“-Anweisungen auf Letzteres ab (S. 1). Nach weiteren 2 Sekunden wird p1 abgelehnt und die von der Catch-Methode angegebene Rückruffunktion ausgelöst. (2) Der Parameter ist kein Objekt mit einer then-Methode oder überhaupt kein Objekt Promise.resolve("Erfolg").dann(Funktion(Wert) { // Die Parameter der Methode Promise.resolve werden gleichzeitig an die Callback-Funktion übergeben. console.log(Wert); // "Erfolgreich" }, Funktion(Wert) { // wird nicht aufgerufen }); (3) Ohne Parameter Die Methode Promise.resolve() ermöglicht Ihnen den Aufruf ohne Parameter und gibt direkt ein Promise-Objekt im aufgelösten Zustand zurück. Wenn Sie ein Promise-Objekt erhalten möchten, ist der direkte Aufruf der Methode Promise.resolve() bequemer. Versprechen.auflösen().dann(Funktion () { console.log('zwei'); }); konsole.log('eins'); // eins zwei (4) Der Parameter ist ein thenable-Objekt Ein thenable-Objekt verweist auf ein Objekt mit einer then-Methode. Die Methode Promise.resolve konvertiert dieses Objekt in ein Promise-Objekt und führt dann sofort die then-Methode des thenable-Objekts aus. lass dann = { dann: Funktion(auflösen, ablehnen) { Entschlossenheit (42); } }; lass p1 = Promise.resolve(thenable); p1.dann(Funktion(Wert) { console.log(Wert); // 42 }); 2. Versprechen.ablehnen()Die Methode Promise.reject() gibt ein Promise-Objekt mit einem Ablehnungsgrund zurück. neues Versprechen((lösen,ablehnen) => { ablehnen(neuer Fehler("Fehler")); }); //Entspricht Promise.reject(new Error("Error")); // Verwendungsmethode Promise.reject(new Error("BOOM!")).catch(error => { konsole.fehler(fehler); }); Es ist erwähnenswert, dass nach dem Aufruf von „Resolve“ oder „Reject“ die Mission von Promise abgeschlossen ist und nachfolgende Vorgänge in der then-Methode platziert werden sollten und nicht direkt nach „Resolve“ oder „Reject“ geschrieben werden sollten. Daher ist es besser, ihnen eine Return-Anweisung voranzustellen, damit es keine Überraschungen gibt. neues Versprechen((lösen, ablehnen) => { Rückgabe ablehnen(1); // Die folgenden Anweisungen werden nicht ausgeführt console.log(2); }) 3. Versprechen.alles()lass p1 = Promise.resolve(1) lass p2 = neues Versprechen(auflösen => { setzeTimeout(() => { Entschlossenheit(2) }, 1000) }) lass p3 = Promise.resolve(3) Versprechen.alles([p3, p2, p1]) .dann(Ergebnis => { // Die Ergebnisse werden in der Reihenfolge zurückgegeben, in der die Instanzen in das Array geschrieben werden console.log(result) // [ 3, 2, 1 ] }) .catch(Grund => { console.log("fehlgeschlagen:Grund") }) Promise.all generiert und gibt ein neues Promise-Objekt zurück, sodass es alle Methoden der Promise-Instanz verwenden kann. Diese Methode wird zurückgegeben, wenn alle als Parameter übergebenen Promise-Objekte im Promise-Array aufgelöst wurden und das neu erstellte Promise die Werte dieser Promises verwendet. Wenn eines der Promises in den Parametern abgelehnt wird, wird der gesamte Promise.all-Aufruf sofort beendet und ein neues abgelehntes Promise-Objekt zurückgegeben. 4. Versprechen.allSettled()Manchmal interessieren uns die Ergebnisse asynchroner Vorgänge nicht, sondern nur, ob diese Vorgänge abgeschlossen sind. Derzeit ist die von ES2020 eingeführte Methode Promise.allSettled() sehr nützlich. Ohne diese Methode wäre es schwierig sicherzustellen, dass alle Vorgänge abgeschlossen werden. Dies ist mit der Methode Promise.all() nicht möglich. Angenommen, es gibt ein solches Szenario: Eine Seite hat drei Bereiche, die drei unabhängigen Schnittstellendaten entsprechen, und Promise.all wird verwendet, um die drei Schnittstellen gleichzeitig anzufordern. Wenn eine der Schnittstellen eine Ausnahme aufweist, lautet der Status „Abgelehnt“, was dazu führt, dass die Daten der drei Bereiche auf der Seite nicht ausgegeben werden. Diese Situation ist für uns offensichtlich inakzeptabel. Das Aufkommen von Promise.allSettled kann dieses Problem lösen: Versprechen.allSettled([ Promise.reject({ Code: 500, Nachricht: 'Serviceausnahme' }), Versprechen.resolve({ Code: 200, Liste: [] }), Versprechen.resolve({ Code: 200, Liste: [] }) ]).dann(res => { Konsole.log(res) /* 0: {Status: "abgelehnt", Grund: {…}} 1: {Status: "erfüllt", Wert: {…}} 2: {Status: "erfüllt", Wert: {…}} */ // Den abgelehnten Status herausfiltern und sicherstellen, dass so viele Seitenbereichsdaten wie möglich vorliegen RenderContent( res.filter(el => { return el.status !== 'abgelehnt' }) ) }) Promise.allSettled ist Promise.all ähnlich. Es nimmt ein Array von Promises als Parameter und gibt ein neues Promise zurück. Der einzige Unterschied besteht darin, dass es keinen Kurzschluss gibt. Das heißt, wenn alle Promises verarbeitet sind, können wir den Status jedes Promises abrufen, unabhängig davon, ob es erfolgreich verarbeitet wurde. 5. Versprechen.race()Die Auswirkung der Methode Promise.all() ist: „Wer langsamer läuft, führt den Rückruf aus.“ Daher gibt es eine andere Methode, die der Aussage „Wer schneller läuft, führt den Rückruf aus“ entspricht: die Methode Promise.race(). Das Wort bedeutet ursprünglich „Rennen“. Die Verwendung von „Race“ ist die gleiche wie bei „All“, wobei ein Array von Promise-Objekten als Parameter empfangen wird. Promise.all wird die nachfolgende Verarbeitung erst fortsetzen, wenn alle empfangenen Objekt-Promises erfüllt oder abgelehnt wurden. Im Gegensatz dazu wird Promise.race die nachfolgende Verarbeitung fortsetzen, solange ein Promise-Objekt den Status „Erfüllt“ oder „Abgelehnt“ erreicht. // Führe die Auflösung nach `delay` Millisekunden aus Funktion TimerPromisify(Verzögerung) { gib ein neues Versprechen zurück (Auflösen => { setzeTimeout(() => { Lösung (Verzögerung); }, Verzögerung); }); } // Wenn ein Versprechen eingelöst oder abgelehnt wird, beendet das Programm die Ausführung von Promise.race([ TimerPromisefy(1), TimerVersprechen(32), TimerVersprechen(64) ]).dann(Funktion (Wert) { console.log(Wert); // => 1 }); Der obige Code erstellt drei Promise-Objekte, die jeweils nach 1 ms, 32 ms und 64 ms bestätigt werden, d. h. FulFilled, und die von .then registrierte Rückruffunktion wird 1 ms nach der Bestätigung des ersten Objekts aufgerufen. 6. Versprechen.prototype.finally()ES9 fügt eine finally()-Methode hinzu, die ein Promise zurückgibt. Am Ende des Versprechens, unabhängig davon, ob es erfüllt oder abgelehnt wird, wird die angegebene Rückruffunktion ausgeführt. Auf diese Weise kann Code übergeben werden, der ausgeführt werden muss, unabhängig davon, ob das Versprechen erfolgreich erfüllt wurde oder nicht. Dadurch wird die Situation vermieden, dass dieselbe Anweisung einmal sowohl in then() als auch in catch() geschrieben werden muss. Beispielsweise wird vor dem Senden einer Anfrage ein Ladebildschirm angezeigt. Nachdem die Anfrage gesendet wurde, hoffen wir, den Ladebildschirm ausschalten zu können, unabhängig davon, ob die Anfrage einen Fehler enthält oder nicht. dies.laden = wahr Anfrage() .then((res) => { // etwas tun }) .fangen(() => { // Fehler protokollieren }) .finally(() => { dies.laden = falsch }) Die Rückruffunktion der Finally-Methode akzeptiert keine Parameter, was darauf hinweist, dass die Vorgänge in der Finally-Methode unabhängig vom Status sein sollten und nicht vom Ausführungsergebnis von Promise abhängen. Praktische Anwendung Angenommen, es besteht eine solche Anforderung: Das rote Licht leuchtet alle 3 Sekunden einmal auf, das grüne Licht leuchtet alle 1 Sekunde einmal und das gelbe Licht leuchtet alle 2 Sekunden einmal auf. Wie können wir dafür sorgen, dass die drei Lichter abwechselnd und wiederholt aufleuchten? Funktion rot() { konsole.log('rot'); } Funktion grün() { konsole.log('grün'); } Funktion gelb() { konsole.log('gelb'); } Die Komplexität dieses Problems liegt in der Notwendigkeit, „abwechselnd und wiederholt“ zu leuchten, anstatt eines einmaligen Deals, der nach einmaligem Leuchten endet. Dies können wir durch Rekursion erreichen: // Implementierung mit Versprechen let task = (timer, light) => { returniere neues Promise((lösen, ablehnen) => { setzeTimeout(() => { wenn (Licht === 'rot') { Rot() } wenn (Licht === 'grün') { Grün() } wenn (Licht === 'gelb') { Gelb() } lösen() }, Zeitgeber); }) } lass Schritt = () => { Aufgabe (3000, „rot“) .then(() => Aufgabe(1000, 'grün')) .then(() => Aufgabe(2000, 'gelb')) .dann(Schritt) } Schritt() Dasselbe kann auch durch async/await erreicht werden: // async/await-Implementierung let step = async () => { warte auf Aufgabe (3000, „rot“) warte auf Aufgabe (1000, „grün“) warte auf Aufgabe (2000, „gelb“) Schritt() } Schritt() Mit async/await können Sie asynchronen Code im Stil von synchronem Code schreiben. Es besteht kein Zweifel, dass die async/await-Lösung intuitiver ist, aber ein tiefes Verständnis von Promise ist die Grundlage für die Beherrschung von async/await. Oben sind die Details der asynchronen Programmierung in Javascript aufgeführt. Weitere Informationen zur asynchronen Programmierung in Javascript finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Zusammenfassung der bei der Arbeit häufig verwendeten Linux-Befehle
>>: So konfigurieren Sie eine JDK-Umgebung unter Linux
„Seiten-Screenshot“ ist eine Anforderung, die häu...
HTML-img-Tag: definiert ein Bild, das in eine Webs...
Heute Morgen hatte ich vor, mit Wampserver eine P...
Hintergrund Es gibt einen Tencent Linux Cloud-Hos...
Für Frontend-Entwickler ist es eine zeitaufwändig...
Dieser Artikel veranschaulicht anhand eines Beisp...
Inhaltsverzeichnis 1. Schreiben Sie Webshell in d...
Betriebssystem: Alibaba Cloud ESC-Instanz centos7...
Wenn vue2 Zeitstempel konvertiert, verwendet es i...
Umfeld Linux 3.10.0-693.el7.x86_64 Docker-Version...
1 Schritte zur Systeminstallation Betriebssystemv...
Docker-Daemon-Socket Der Docker-Daemon kann über ...
Vorwort Sehen wir uns zunächst an, wie eine TCP-V...
Vorwort Obwohl der Feiertag vorbei ist, zeigt er ...
Einfaches Beispiel für das Hinzufügen und Entfern...