Promise-Objekte vereinfachen verzögerte und asynchrone Berechnungen. Ein Promise stellt einen noch nicht abgeschlossenen Vorgang dar.
Bereiten Sie sich auf einen entscheidenden Moment in der Geschichte des Webentwicklung.
[Trommelwirbel beginnt]
Promise-Objekte sind in JavaScript eingegangen.
[Feuerwerk explodieren, glitzerndes Papier regnet von oben, die Menge ist wild]
An dieser Stelle fallen Sie in eine der folgenden Kategorien:
- Die Leute jubeln um Sie herum, aber Sie sind sich nicht sicher, was das Ganze ist über. Vielleicht sind Sie sich nicht einmal sicher, ist. Sie würden die Achseln zucken, aber Das Gewicht von glitzerndem Papier liegt auf Ihren Schultern. Wenn ja, sollten Sie Es dauerte eine ganze Weile, bis ich herausgefunden hatte, Vielleicht möchten Sie von vorn beginnen.
- Du schlagst die Luft! Zeit, richtig? Du hast diese Promise schon einmal verwendet Allerdings stört es Sie, dass alle Implementierungen eine etwas andere API haben. Wie lautet die API für die offizielle JavaScript-Version? Beginnen Sie am besten damit, durch die Terminologie.
- Das wussten Sie schon und Sie verspotten diejenigen, die aufspringen und wie Nachrichten für sie. Nehmen Sie sich einen Moment Zeit, Rufen Sie dann direkt die API-Referenz auf.
Browserunterstützung und Polyfill
Unterstützte Browser
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
Um Browser ohne vollständige Versprechen an die Spezifikationen anzupassen oder anderen Browsern und Node.js Versprechen hinzufügen, Polyfill (2 K gzip-Datei)
Was soll das Ganze?
JavaScript ist ein Single-Thread-Element, d. h., zwei Skripte können nicht gleichzeitig ausgeführt werden. zur gleichen Zeit wechseln. müssen sie nacheinander ausgeführt werden. In Browsern zeigt JavaScript einen Thread mit einer Vielzahl anderer Inhalte teilt, die sich von Browser zu Browser unterscheiden. Browser. Normalerweise befindet sich JavaScript in derselben Warteschlange wie das Painting, sowie die Verarbeitung von Nutzeraktionen (z. B. Hervorheben von Text und Interaktionen mit Formularsteuerelementen). Aktivitäten in einem dieser Dinge verzögern die anderen.
Mensch ist ein Multithread. Sie können Text mit mehreren Fingern eingeben, Sie können gleichzeitig eine Unterhaltung führen und führen. Die einzige Blockierung mit der wir uns befassen müssen, ist das Niesen, bei dem alle aktuellen Aktivitäten während des Niesens ausgesetzt werden. Das ist ziemlich ärgerlich, vor allem, wenn Sie Auto fahren und versuchen, ein Gespräch zu führen. Das solltest du nicht tun Code schreiben wollen, der einfach nicht passt.
Um dies zu umgehen, haben Sie wahrscheinlich Ereignisse und Callbacks verwendet. Hier sind Ereignisse:
var img1 = document.querySelector('.img-1');
img1.addEventListener('load', function() {
// woo yey image loaded
});
img1.addEventListener('error', function() {
// argh everything's broken
});
Das ist gar nicht lustig. Wir rufen das Bild ab, fügen einige Zuhörer hinzu JavaScript kann die Ausführung stoppen, bis einer dieser Listener aufgerufen wird.
Leider ist es im obigen Beispiel möglich, dass die Ereignisse bevor wir sie hören. Deshalb müssen wir die „vollständigen“ Eigenschaft von Bildern:
var img1 = document.querySelector('.img-1');
function loaded() {
// woo yey image loaded
}
if (img1.complete) {
loaded();
}
else {
img1.addEventListener('load', loaded);
}
img1.addEventListener('error', function() {
// argh everything's broken
});
Bilder mit Fehlern werden nicht erkannt, bevor wir Leider bietet das DOM dafür keine Möglichkeit. Dies ist außerdem wird ein Bild geladen. Es wird noch komplexer, wenn wir wissen möchten, von Bildern wurden geladen.
Veranstaltungen sind nicht immer optimal
Ereignisse eignen sich hervorragend für Dinge, die mehrmals am selben Tag stattfinden können.
Objekt: keyup
, touchstart
usw. Diese Ereignisse sind für Sie nicht relevant.
was vor der Verbindung
mit dem Hörer passiert ist. Aber wenn es darum geht,
asynchronen Erfolg/Misserfolg haben, sollten Sie etwa Folgendes benötigen:
img1.callThisIfLoadedOrWhenLoaded(function() {
// loaded
}).orIfFailedCallThis(function() {
// failed
});
// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
// all loaded
}).orIfSomeFailedCallThis(function() {
// one or more failed
});
So etwas ist durch Versprechen möglich, allerdings mit besserer Benennung. Hätten HTML-Bildelemente ein „ready“ die ein Promise zurückgegeben hat, könnten wir Folgendes tun:
img1.ready()
.then(function() {
// loaded
}, function() {
// failed
});
// and…
Promise.all([img1.ready(), img2.ready()])
.then(function() {
// all loaded
}, function() {
// one or more failed
});
Im Grunde ähneln Versprechen wie Event-Listener, mit folgenden Ausnahmen:
- Ein Promise kann nur einmal erfolgreich sein oder fehlschlagen. Sie kann nicht zweimal erfolgreich oder scheitern, und umgekehrt.
- Wenn ein Promise erfolgreich oder fehlgeschlagen ist und du später einen Erfolg/Misserfolg hinzufügst wird der richtige Callback aufgerufen, obwohl das Ereignis früher platziert.
Dies ist bei einem asynchronen Erfolg/Scheitern äußerst nützlich, da Sie weniger interessiert daran, wann genau etwas verfügbar ist, auf das Ergebnis zu reagieren.
Promise-Terminologie
Proof von Domenic Denicola: ersten Entwurf lesen dieses Artikels und bewertete mich mit "F". für Terminologie. Er hat mich inhaftiert, hat mich gezwungen, Zustände und Schicksale und einen besorgten Brief an meine Eltern geschrieben. Trotzdem habe ich die Begriffe verwechseln, aber hier sind die Grundlagen:
Ein Promise kann Folgendes sein:
- fulfill – Aktion in Bezug auf das Versprechen erfolgreich
- rejected – Die Aktion in Bezug auf das Versprechen ist fehlgeschlagen.
- ausstehend – Wurde noch nicht ausgeführt oder abgelehnt
- settled – erfüllt oder abgelehnt
Technische Daten
verwendet den Begriff thenable, um ein Promise-ähnliches Objekt zu beschreiben,
da sie eine then
-Methode hat. Dieser Begriff erinnert mich an den ehemaligen englischen Football
Manager Terry Venables, sodass
Ich werde es so wenig wie möglich verwenden.
Promise-Objekte kommen in JavaScript an!
Promise-Objekte gibt es schon seit einiger Zeit in Form von Bibliotheken, z. B.:
Die oben genannten und die JavaScript-Versprechen haben ein gemeinsames, standardisiertes Verhalten. Promises/A+. Wenn jQuery-Nutzer haben eine ähnliche Funktion: Verzögert: Sie können jedoch Verzögerte Anzeigen sind nicht Promise/A+-konform. geringfügig anders und weniger nützlich, also sei vorsichtig! jQuery enthält auch als Promise bezeichnet, und hat die gleichen Probleme.
Obwohl Promise-Implementierungen einem standardisierten Verhalten folgen, APIs insgesamt unterschiedlich sind. JavaScript-Promis sind in der API ähnlich wie RSVP.js. So erstellst du ein Promise:
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
Der Promise-Konstruktor verwendet ein Argument, einen Callback mit zwei Parametern, klären und ablehnen. Tätigt innerhalb des Callbacks eine Aktion, z. B. asynchron, und ruft dann Klären, wenn alles funktioniert hat, andernfalls „Ablehnen“ aufrufen.
Wie bei throw
in JavaScript ist es üblich, aber nicht zwingend,
mit einem Error-Objekt ablehnen. Fehlerobjekte haben den Vorteil, dass sie
Debugging-Tools hilfreicher machen.
So verwendest du dieses Promise:
promise.then(function(result) {
console.log(result); // "Stuff worked!"
}, function(err) {
console.log(err); // Error: "It broke"
});
then()
akzeptiert zwei Argumente, einen Callback für einen erfolgreichen Fall und ein weiteres
für den Fall, dass ein Fehler auftritt. Beide sind optional. Sie können also einen Callback für die
Erfolg oder Misserfolg.
JavaScript-Versprechen begannen im DOM als "Futures", umbenannt in "Promises", und schließlich in JavaScript verschoben. Wenn sie in JavaScript gespeichert sind, DOM ist besonders nützlich, weil sie auch in nicht browsergestützten JS-Kontexten verfügbar sind, z. B. Node.js (ob sie diese in ihren Kern-APIs verwenden, ist eine andere Frage).
Obwohl es sich um eine JavaScript-Funktion handelt, kann das DOM sie problemlos verwenden. In Alle neuen DOM-APIs mit asynchronen Erfolgs-/Fehlermethoden verwenden Versprechen. Dies geschieht bereits bei Kontingentverwaltung, Font Load-Ereignisse, ServiceWorker Web-MIDI Streams und mehr
Kompatibilität mit anderen Bibliotheken
Die JavaScript Promis API behandelt alles, was eine then()
-Methode enthält, wie
Versprechen-ähnlich (oder thenable
in Seufz), wenn du also eine Bibliothek verwendest
ein Q-Versprechen zurückgegeben, kein Problem, es passt gut zum neuen
JavaScript verspricht.
Obwohl, wie bereits erwähnt, sind die Deferreds von jQuery ein wenig... nicht hilfreich. Zum Glück kannst du sie wie gewohnt einsetzen, was sich lohnen würde so schnell wie möglich:
var jsPromise = Promise.resolve($.ajax('/whatever.json'))
Hier gibt der $.ajax
von jQuery einen Deferred. Da sie eine then()
-Methode hat,
Promise.resolve()
kann es in ein JavaScript-Promise umwandeln. Sie können jedoch
Manchmal übergeben deferreds mehrere Argumente an ihre Callbacks. Beispiel:
var jqDeferred = $.ajax('/whatever.json');
jqDeferred.then(function(response, statusText, xhrObj) {
// ...
}, function(xhrObj, textStatus, err) {
// ...
})
JS-Versprechen hingegen ignorieren alle bis auf die erste:
jsPromise.then(function(response) {
// ...
}, function(xhrObj) {
// ...
})
Zum Glück ist dies in der Regel das, was Sie wollen oder zumindest Zugriff auf was Sie möchten. Beachten Sie außerdem, dass jQuery nicht der Konvention Fehlerobjekte in Ablehnungen übergeben.
Einfacher komplexer asynchroner Code
Okay, lassen Sie uns ein paar Dinge programmieren. Angenommen, wir möchten:
- Rotierendes Ladesymbol starten
- Rufen Sie JSON für eine Geschichte ab, das uns den Titel und die URLs für jedes Kapitel liefert.
- Titel zur Seite hinzufügen
- Jedes Kapitel abrufen
- Geschichte zur Seite hinzufügen
- Rotierendes Ladesymbol anhalten
...aber informieren Sie den Nutzer auch, wenn auf dem Weg etwas schiefgelaufen ist. Wir benötigen an dieser Stelle anhalten, sonst dreht es sich weiter, und auf einer anderen Benutzeroberfläche stürzen.
Natürlich würden Sie kein JavaScript verwenden, um eine Geschichte zu erzählen, als HTML ausgeliefert wird, schneller ist, aber dieses Muster ist ziemlich häufig beim Umgang mit APIs: Mehrere Daten und etwas tun, wenn alles erledigt ist.
Beginnen wir mit dem Abrufen von Daten aus dem Netzwerk:
Versprechen von XMLHttpRequest
Alte APIs werden aktualisiert, um Promise zu verwenden, wenn dies in einem umgekehrten Zeitraum möglich ist.
auf kompatible Weise. XMLHttpRequest
ist ein Spitzenkandidaten, aber in der Zwischenzeit
schreiben wir eine einfache Funktion, um eine GET-Anfrage zu stellen:
function get(url) {
// Return a new promise.
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send();
});
}
Gehen wir nun wie folgt vor:
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.error("Failed!", error);
})
Jetzt können wir HTTP-Anfragen senden, ohne XMLHttpRequest
manuell eingeben zu müssen, was gut ist,
weniger um das wütende Kamelformat vor XMLHttpRequest
zu sehen, desto glücklicher wird mein Leben.
Verkettung
then()
ist nicht das Ende der Geschichte, Sie können then
s miteinander verbinden, um
Werte transformieren oder zusätzliche asynchrone Aktionen nacheinander ausführen.
Werte transformieren
Sie können Werte einfach transformieren, indem Sie den neuen Wert zurückgeben:
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
})
Gehen wir als Beispiel aus der Praxis zurück zu:
get('story.json').then(function(response) {
console.log("Success!", response);
})
Die Antwort ist im JSON-Format, aber wir empfangen sie derzeit als Nur-Text. Mi.
die Funktion get ändern,
um die JSON-Datei zu verwenden,
responseType
,
aber wir könnten es auch in Versprechen lösen:
get('story.json').then(function(response) {
return JSON.parse(response);
}).then(function(response) {
console.log("Yey JSON!", response);
})
Da JSON.parse()
ein einzelnes Argument verwendet und einen transformierten Wert zurückgibt,
können wir eine Verknüpfung erstellen:
get('story.json').then(JSON.parse).then(function(response) {
console.log("Yey JSON!", response);
})
Tatsächlich könnten wir eine getJSON()
-Funktion ganz einfach erstellen:
function getJSON(url) {
return get(url).then(JSON.parse);
}
getJSON()
gibt trotzdem ein Promise zurück, das eine URL abruft und dann parst
die Antwort im JSON-Format.
Asynchrone Aktionen in die Warteschlange stellen
Sie können then
s auch verketten, um asynchrone Aktionen nacheinander auszuführen.
Es ist wie von Zauberhand, wenn du etwas von einem then()
-Callback zurückgibst.
Wenn Sie einen Wert zurückgeben, wird die nächste then()
mit diesem Wert aufgerufen. Sie können jedoch
wenn Sie etwas wie ein Versprechen zurückgeben,
wartet das nächste then()
darauf und wird
nur aufgerufen, wenn das Versprechen abgeschlossen ist (erfolgreich/fehlgeschlagen). Beispiel:
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
})
Hier senden wir eine asynchrone Anfrage an story.json
, die uns eine Reihe von
zu beantragen, fordern wir die erste URL an. Das ist der Zeitpunkt, an dem
sich von einfachen Rückrufmustern abzuheben.
Du kannst sogar eine Tastenkombination zum Abrufen von Kapiteln einrichten:
var storyPromise;
function getChapter(i) {
storyPromise = storyPromise || getJSON('story.json');
return storyPromise.then(function(story) {
return getJSON(story.chapterUrls[i]);
})
}
// and using it is simple:
getChapter(0).then(function(chapter) {
console.log(chapter);
return getChapter(1);
}).then(function(chapter) {
console.log(chapter);
})
story.json
wird erst heruntergeladen, wenn getChapter
aufgerufen wird, aber der nächste
„getChapter
“ wird als „wiederverwendetes Story-Versprechen“ bezeichnet, daher: story.json
wird nur einmal abgerufen. Geschworen!
Fehlerbehandlung
Wie bereits erwähnt, gibt es für then()
zwei Argumente, eines für den Erfolg, eins
(oder erfüllen und ablehnen, wie es versprochen wird):
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.log("Failed!", error);
})
Sie können auch catch()
verwenden:
get('story.json').then(function(response) {
console.log("Success!", response);
}).catch(function(error) {
console.log("Failed!", error);
})
catch()
hat nichts Besonderes. Es ist nur Zucker für
then(undefined, func)
, ist aber besser lesbar. Beachten Sie, dass die beiden Codes
verhalten sich die Beispiele oben nicht gleich. Letzteres entspricht:
get('story.json').then(function(response) {
console.log("Success!", response);
}).then(undefined, function(error) {
console.log("Failed!", error);
})
Der Unterschied ist zwar subtil, aber äußerst nützlich. Abgelehnte Ablehnungen werden übersprungen
mit einem Ablehnungs-Callback zur nächsten then()
weiterleiten (oder catch()
, da
gleichwertig). Mit then(func1, func2)
werden func1
oder func2
und niemals beides. Aber mit then(func1).catch(func2)
sind beide
wird aufgerufen, wenn func1
ablehnt, da es sich um separate Schritte in der Kette handelt. Nehmen
Folgendes:
asyncThing1().then(function() {
return asyncThing2();
}).then(function() {
return asyncThing3();
}).catch(function(err) {
return asyncRecovery1();
}).then(function() {
return asyncThing4();
}, function(err) {
return asyncRecovery2();
}).catch(function(err) {
console.log("Don't worry about it");
}).then(function() {
console.log("All done!");
})
Der obige Ablauf ähnelt sehr dem normalen JavaScript-Versuch/-Catch, bei dem Fehler, die
während eines „Versuchs“ wechseln Sie direkt zum catch()
-Block. Hier ist die
(weil ich Flussdiagramme liebe):
Folge den blauen Linien für Versprechungen, die erfüllt werden, die roten Linien für Versprechen, die ablehnen.
JavaScript-Ausnahmen und -Promis
Ablehnungen treten auf, wenn ein Promise explizit, aber auch implizit abgelehnt wird. wenn im Konstruktor-Callback ein Fehler ausgegeben wird:
var jsonPromise = new Promise(function(resolve, reject) {
// JSON.parse throws an error if you feed it some
// invalid JSON, so this implicitly rejects:
resolve(JSON.parse("This ain't JSON"));
});
jsonPromise.then(function(data) {
// This never happens:
console.log("It worked!", data);
}).catch(function(err) {
// Instead, this happens:
console.log("It failed!", err);
})
Es ist also sinnvoll, alle Ihre Promise-bezogenen Arbeiten innerhalb der Promise Konstruktor-Rückruf zurück, sodass Fehler automatisch erkannt und zu Ablehnungen werden.
Dasselbe gilt für Fehler in then()
-Callbacks.
get('/').then(JSON.parse).then(function() {
// This never happens, '/' is an HTML page, not JSON
// so JSON.parse throws
console.log("It worked!", data);
}).catch(function(err) {
// Instead, this happens:
console.log("It failed!", err);
})
Fehlerbehandlung in der Praxis
Mit unserer Geschichte und den Kapiteln können wir einen Catch verwenden, um den Nutzenden einen Fehler anzuzeigen:
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
addHtmlToPage(chapter1.html);
}).catch(function() {
addTextToPage("Failed to show chapter");
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Wenn das Abrufen von story.chapterUrls[0]
fehlschlägt (z.B. HTTP 500 oder der Nutzer offline ist),
alle folgenden erfolgreichen Callbacks übersprungen, einschließlich des
getJSON()
, die versucht, die Antwort als JSON zu parsen, und überspringt außerdem den
-Callback, der der Seite kapitel1.html hinzufügt. Stattdessen geht es am Fang weiter.
Callback des Nutzers an. Deshalb wird die Meldung „Kapitel konnte nicht angezeigt werden“ angezeigt. wird der Seite hinzugefügt, wenn
eine der vorherigen Aktionen ist fehlgeschlagen.
Wie bei der Methode „try/catch“ von JavaScript wird der Fehler abgefangen und der fährt fort, sodass das Kreiselsymbol immer ausgeblendet ist, was wir wollen. Die wird zu einer nicht blockierenden asynchronen Version von:
try {
var story = getJSONSync('story.json');
var chapter1 = getJSONSync(story.chapterUrls[0]);
addHtmlToPage(chapter1.html);
}
catch (e) {
addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'
Sie können catch()
nur zu Protokollierungszwecken verwenden, ohne eine Wiederherstellung durchzuführen.
aus dem Fehler. Geben Sie dazu den Fehler einfach noch einmal aus. Das könnten wir in
Unsere getJSON()
-Methode:
function getJSON(url) {
return get(url).then(JSON.parse).catch(function(err) {
console.log("getJSON failed for", url, err);
throw err;
});
}
Wir haben es geschafft, ein Kapitel zu holen, aber wir möchten alle. Lassen Sie uns auftreten können.
Parallelität und Sequenzierung: Das Beste aus beidem erhalten
Asynchron zu denken, ist nicht einfach. Wenn Sie Schwierigkeiten haben, aus dem Weg zu gehen, schreiben Sie den Code so, als wäre er synchron. In diesem Fall gilt:
try {
var story = getJSONSync('story.json');
addHtmlToPage(story.heading);
story.chapterUrls.forEach(function(chapterUrl) {
var chapter = getJSONSync(chapterUrl);
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}
catch (err) {
addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none'
Das funktioniert! Er wird jedoch synchronisiert und sperrt den Browser währenddessen. Bis
Wenn diese Vorgänge asynchron sind, verwenden wir then()
, damit die Dinge nacheinander stattfinden.
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
// And we're all done!
addTextToPage("All done");
}).catch(function(err) {
// Catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// Always hide the spinner
document.querySelector('.spinner').style.display = 'none';
})
Aber wie können wir durch die URLs der Kapitel suchen und sie der Reihe nach abrufen? Dieses funktioniert nicht:
story.chapterUrls.forEach(function(chapterUrl) {
// Fetch chapter
getJSON(chapterUrl).then(function(chapter) {
// and add it to the page
addHtmlToPage(chapter.html);
});
})
forEach
ist nicht asynchron, unsere Kapitel würden also in beliebiger Reihenfolge angezeigt werden
sie herunterladen, wie Pulp Fiction geschrieben wurde. Das ist nicht
Pulp Fiction, lösen wir das Problem.
Sequenz erstellen
Wir möchten das Array chapterUrls
in eine Folge von Versprechen umwandeln. Dazu können wir then()
verwenden:
// Start off with a promise that always resolves
var sequence = Promise.resolve();
// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
// Add these actions to the end of the sequence
sequence = sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
})
Zum ersten Mal sehen wir Promise.resolve()
, mit dem ein
das auf den Wert aufgelöst wird,
den Sie ihm geben. Wenn Sie sie bestehen,
Instanz von Promise
wird sie einfach zurückgegeben (Hinweis:Dies ist ein
an die Spezifikation, die für einige Implementierungen noch nicht gilt). Wenn Sie
Etwas versprochenes weitergeben (eine then()
-Methode hat), erstellt sie eine
Echte Promise
, die auf die gleiche Weise erfüllt/abgelehnt werden. Bei bestandener Prüfung
in einem anderen Wert, z.B. Promise.resolve('Hello')
erstellt, wird ein
das diesen Wert erfüllt. Wenn Sie es ohne Wert nennen,
wie oben beschrieben, erfüllt sie sich mit „nicht definiert“.
Da ist auch Promise.reject(val)
, das ein Versprechen abgibt, das mit
den Wert, den Sie ihr geben (oder nicht definiert).
Wir können den obigen Code mit
array.reduce
:
// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Add these actions to the end of the sequence
return sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve())
Es wird derselbe Schritt wie im vorherigen Beispiel ausgeführt, es ist jedoch nicht die separate
"Sequenz" . Unser Reduce-Callback wird für jedes Element im Array aufgerufen.
"Sequenz" beim ersten Mal Promise.resolve()
, aber für den Rest des
ruft „Sequenz“ auf was wir vom vorherigen Aufruf zurückgegeben haben. array.reduce
ist sehr nützlich, um ein Array auf einen einzigen Wert zu reduzieren,
ist ein Versprechen.
Fassen wir nun alles zusammen:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
return story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Once the last chapter's promise is done…
return sequence.then(function() {
// …fetch the next chapter
return getJSON(chapterUrl);
}).then(function(chapter) {
// and add it to the page
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
// And we're all done!
addTextToPage("All done");
}).catch(function(err) {
// Catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// Always hide the spinner
document.querySelector('.spinner').style.display = 'none';
})
Und da haben wir es, eine vollständig asynchrone Version der Synchronisierungsversion. Aber wir können besser machen. Derzeit wird unsere Seite wie folgt heruntergeladen:
Browser sind ziemlich gut darin, mehrere Inhalte auf einmal herunterzuladen. indem du Kapitel nacheinander herunterlädst. Unser Ziel ist es, alle gleichzeitig herunterladen und verarbeiten, sobald sie alle sind. Zum Glück gibt es dafür eine API:
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
//...
})
Promise.all
nimmt eine Reihe von Versprechen und hinterlässt ein Versprechen, das erfüllt wird
wenn sie alle erfolgreich abgeschlossen wurden. Sie erhalten ein Array von Ergebnissen (je nachdem,
der Versprechen erfüllt werden) und zwar in derselben Reihenfolge wie die Versprechen, die du gegeben hast.
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Take an array of promises and wait on them all
return Promise.all(
// Map our array of chapter urls to
// an array of chapter json promises
story.chapterUrls.map(getJSON)
);
}).then(function(chapters) {
// Now we have the chapters jsons in order! Loop through…
chapters.forEach(function(chapter) {
// …and add to the page
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened so far
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Je nach Verbindung kann dies Sekunden schneller sein als ein einzelner Ladevorgang. und es ist weniger Code als beim ersten Versuch. Die Kapitel können in einem beliebigen in der richtigen Reihenfolge auf dem Bildschirm erscheinen.
Die wahrgenommene Leistung lässt sich jedoch trotzdem verbessern. Wenn Kapitel eins da ist, um es auf der Seite hinzuzufügen. So können Nutzende mit dem Lesen beginnen, bevor der die Kapitel sind da. Wenn Kapitel 3 kommen, würden wir es nicht da der Nutzer möglicherweise nicht bemerkt, dass Kapitel 2 fehlt. Wenn Kapitel 2 können wir Kapitel 2 und 3 usw. hinzufügen.
Dazu rufen wir JSON für alle unsere Kapitel gleichzeitig ab und erstellen dann ein um sie dem Dokument hinzuzufügen:
getJSON('story.json')
.then(function(story) {
addHtmlToPage(story.heading);
// Map our array of chapter urls to
// an array of chapter json promises.
// This makes sure they all download in parallel.
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// Use reduce to chain the promises together,
// adding content to the page for each chapter
return sequence
.then(function() {
// Wait for everything in the sequence so far,
// then wait for this chapter to arrive.
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Und das Beste von beidem! Die Bereitstellung dauert genauso lange, der gesamte Inhalt, aber der Nutzer erhält den ersten Teil des Inhalts früher.
In diesem einfachen Beispiel kommen alle Kapitel zur gleichen Zeit an, aber ist der Vorteil der einzelnen Anzeigen durch mehrere, größere und Kapiteln.
Dazu verwenden Sie Callbacks im Node.js-Stil oder Events in der Nähe den Code verdoppeln, aber, was noch wichtiger ist, nicht so einfach zu befolgen ist. Dieses ist aber noch nicht das Ende der Geschichte für Versprechen, in Kombination mit anderen ES6-Funktionen. wird es sogar noch einfacher.
Zusatzrunde: erweiterte Funktionen
Seit ich diesen Artikel geschrieben habe, können Promise-Objekte jetzt sehr wichtig. Seit Chrome 55 ist es dank asynchroner Funktionen möglich, versprochenen Code als synchron geschrieben, aber ohne den Hauptthread zu blockieren. Sie können Weitere Informationen dazu finden Sie in meinem Artikel zu asynchronen Funktionen. Es gibt Umfassende Unterstützung sowohl für Promise- als auch für asynchrone Funktionen in den wichtigsten Browsern. Einzelheiten hierzu finden Sie in der Versprechen und asynchron Funktion Referenz.
Vielen Dank an Anne van Kesteren, Domenic Denicola, Tom Ashworth, Remy Sharp, Addy Osmani, Arthur Evans und Yutaka Hirano, die dies Korrektur gelesen und Korrekturen/Empfehlungen.
Vielen Dank auch an Mathias Bynens für um verschiedene Teile zu aktualisieren des Artikels.