Das Objekt Promise steht für den schließlichen Abschluss (oder das Scheitern) einer asynchronen Operation und ihres resultierenden Werts.
Dies ist für viele ein schwieriges Thema, besonders wenn du Programmieren in einer Sprache gelernt hast, die völlig synchron ist. Wenn du dich überfordert fühlst oder mehr über Nebenläufigkeit und Parallelität erfahren möchtest, dann schau ihn dir über go.dev an oder schau ihn dir direkt auf Vimeo an und lies die Folien zu dem brillanten Vortrag „Concurrency is not parallelism".
Ein Promise hat drei Zustände:
Wenn es erstellt wird, ist ein Promise pending. Irgendwann in der Zukunft kann es aufgelöst oder abgelehnt werden. Sobald ein Promise einmal aufgelöst oder abgelehnt wurde, kann es nie wieder aufgelöst oder abgelehnt werden, und sein Zustand kann sich auch nicht mehr ändern.
Mit anderen Worten:
Ein Promise kann auf verschiedene Arten aufgelöst werden:
// Creates a promise that is immediately resolved
Promise.resolve(value);
// Creates a promise that is immediately resolved
new Promise((resolve) => {
resolve(value);
});
// Chaining a promise leads to a resolved promise
somePromise.then(() => {
// ...
return value;
});
In den Beispielen oben kann value alles sein, auch ein Fehler, undefined, null oder ein weiteres Promise.
Normalerweise möchtest du mit einem Wert auflösen, der kein Fehler ist.
Ein Promise kann auf verschiedene Arten abgelehnt werden:
// Creates a promise that is immediately rejected
Promise.reject(reason)
// Creates a promise that is immediately rejected
new Promise((_, reject) {
reject(reason)
})
// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
// ...
throw reason
})
In den Beispielen oben kann reason alles sein, auch ein Fehler, undefined oder null.
Normalerweise möchtest du mit einem Fehler ablehnen.
Ein Promise kann mit einer zukünftigen Aktion fortgesetzt werden, sobald es aufgelöst oder abgelehnt wird.
promise.then() wird aufgerufen, sobald promise aufgelöst wirdpromise.catch() wird aufgerufen, sobald promise abgelehnt wirdpromise.finally() wird aufgerufen, sobald promise aufgelöst oder abgelehnt wirdJedes Promise ist „thenable".
Das heißt, es gibt eine Funktion then, die ausgeführt wird, sobald das ursprüngliche Promise aufgelöst wird.
Mit promise.then(onResolved) erhält der Callback onResolved den Wert, mit dem das ursprüngliche Promise aufgelöst wurde.
Dies gibt immer ein neues „verkettetes" Promise zurück.
Wenn du einen value aus then zurückgibst, wird das „verkettete" Promise aufgelöst.
Wenn du einen reason in then wirfst, wird das „verkettete" Promise abgelehnt.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
const promise2 = promise1.then(function (value) {
console.log(value);
// expected output: "Success!"
return true;
});
Dies gibt nach ungefähr 1000 ms "Success!" aus.
Der Zustand und der Wert von promise1 sind resolved und "Success!".
Der Zustand und der Wert von promise2 sind resolved und true.
Es gibt ein zweites Argument, das ausgeführt wird, wenn das ursprüngliche Promise abgelehnt wird.
Mit promise.then(onResolved, onRejected) erhält der Callback onResolved den Wert, mit dem das ursprüngliche Promise aufgelöst wurde, oder der Callback onRejected erhält den Grund, aus dem das Promise abgelehnt wurde.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return true;
}
function shout(reason) {
console.error(reason.toUpperCase());
return false;
}
const promise2 = promise1.then(log, shout);
"Success!" aus.
promise1 sind resolved und "Success!".promise2 sind resolved und true."NOPE!" aus.
promise1 sind rejected und Nope!.promise2 sind resolved und false.Es ist wichtig zu verstehen, dass aufgrund der Regeln des Lebenszyklus das resolve, das etwa 1000 ms später kommt, stillschweigend ignoriert wird, wenn es rejectt, da sich der interne Zustand nicht mehr ändern kann, sobald er abgelehnt oder aufgelöst wurde.
Es ist wichtig zu verstehen, dass das Zurückgeben eines Werts aus einem Promise dieses auflöst und das Werfen eines Werts es ablehnt.
Wenn promise1 aufgelöst wird und ein verkettetes onResolved vorhanden ist: then(onResolved), dann ist diese Fortsetzung ein neues Promise, das aufgelöst oder abgelehnt werden kann.
Wenn promise1 abgelehnt wird, aber ein verkettetes onRejected vorhanden ist: then(, onRejected), dann ist diese Fortsetzung ein neues Promise, das aufgelöst oder abgelehnt werden kann.
Manchmal möchtest du Fehler abfangen und nur fortfahren, wenn das ursprüngliche Promise rejectt.
Mit promise.catch(onCatch) erhält der Callback onCatch den Grund, aus dem das ursprüngliche Promise abgelehnt wurde.
Dies gibt immer ein neues „verkettetes" Promise zurück.
Wenn du einen value aus catch zurückgibst, wird das „verkettete" Promise aufgelöst.
Wenn du einen reason in catch wirfst, wird das „verkettete" Promise abgelehnt.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return 'done';
}
function recover(reason) {
console.error(reason.toUpperCase());
return 42;
}
const promise2 = promise1.catch(recover).then(log);
In etwa der Hälfte der Fälle gibt dies nach ungefähr 1000 ms "Success!" aus.
In der anderen Hälfte der Fälle gibt dies sofort 42 aus.
promise1 aufgelöst wird, wird catch übersprungen, der Aufruf erreicht then und gibt den Wert aus.
promise1 sind resolved und "Success!".promise2 sind resolved und "done";promise1 abgelehnt wird, wird catch ausgeführt, das einen Wert zurückgibt, und damit ist die Kette nun resolved und erreicht then und gibt den Wert aus.
promise1 sind rejected und "Nope!".promise2 sind resolved und "done";Manchmal möchtest du Code ausführen, nachdem ein Promise abgeschlossen ist, egal ob es aufgelöst oder abgelehnt wird.
Mit promise.finally(onSettled) erhält der Callback onSettled nichts.
Dies gibt immer ein neues „verkettetes" Promise zurück.
Wenn du einen value aus finally zurückgibst, werden Zustand und Wert des ursprünglichen Promises kopiert, wobei der value ignoriert wird.
Wenn du einen reason in finally wirfst, wird das „verkettete" Promise abgelehnt, wodurch ein eventueller Zustand, Wert oder Grund des ursprünglichen Promises überschrieben wird.
Mehrere dieser Methoden zusammen:
const myPromise = new Promise(function (resolve, reject) {
const sampleData = [2, 4, 6, 8];
const randomNumber = Math.round(Math.random() * 5);
if (sampleData[randomNumber]) {
resolve(sampleData[randomNumber]);
} else {
reject('Sampling did not result in a sample');
}
});
const finalPromise = myPromise
.then(function (sampled) {
// If the random number was 0, 1, 2, or 3, this will be
// reached and the number 2, 4, 6, or 8 will be logged.
console.log(`Sampled data: ${sampled}`);
return 'yay';
})
.catch(function (reason) {
// If the random number was 4 or 5, this will be reached and
// reason will be "An error occurred". The entire chain will
// then reject with an Error with the reason as message.
throw new Error(reason);
})
.finally(function () {
// This will always log after either the sampled data is
// logged or the error is raised.
console.log('Promise completed');
});
randomNumber 0-3 ist:
myPromise wird mit dem Wert 2, 4, 6, or 8 aufgelöstfinalPromise wird mit dem Wert 'yay' aufgelöstSampled data: ...Promise completedrandomNumber 4-5 ist:
myPromise wird mit dem Grund 'Sampling did not result in a sample' abgelehntfinalPromise wird mit dem Grund Error('Sampling did not result in a sample') abgelehntPromise completed"uncaught rejected promise: Error('Sampling did not result in a sample')"
Wie oben gezeigt, funktioniert reject mit einem String, und ein Promise kann auch mit einem Error ablehnen.
Wenn das Verketten von Promises oder die allgemeine Verwendung unklar ist, ist das Tutorial auf MDN eine gute Ressource zum Nachlesen.
In dieser Übung implementierst du einen TranslationService, der kostenlosen Mitgliedern grundlegende Übersetzungsdienste bietet und Premium-Mitgliedern erweiterte Übersetzungen mit Qualitätsgarantie.
Die API
Du hast eine Übersetzungs-API aus dem Weltraum gefunden, die jede Übersetzungs-request in angemessener Zeit erfüllt.
Das möchtest du dir zunutze machen.
Die Weltraumübersetzer sind extrem launisch und hassen Redundanz. Deshalb betreiben sie auch API-Speicher-Satelliten, aus denen du vergangene Übersetzungen fetchen kannst, ohne sie zu belästigen.
Eine Übersetzung abrufen
api.fetch(text) ruft eine Übersetzung von text aus dem API-Speicher ab und gibt ein promise zurück, das zwei Werte liefert:
translation: die eigentliche Übersetzungquality: die Qualität als ZahlWenn eine Übersetzung nicht im API-Speicher gefunden wird, wirft die API einen NotAvailable-Fehler.
Übersetzungen können mit der Methode api.request hinzugefügt werden.
Wenn 'text' nicht übersetzbar ist, wirft die API einen Untranslatable-Fehler.
api.fetch('jIyaj');
// => Promise({ resolved: 'I understand' })
Eine Übersetzung anfordern
Manche Übersetzungen existieren sicher, wurden aber noch nicht zum API-Speicher hinzugefügt. Das ist der Unterschied zwischen NotAvailable (nicht im Speicher, aber anforderbar) und Untranslatable (nicht übersetzbar).
api.request(text, callback) fordert an, dass eine Übersetzung von text durchgeführt und in den API-Speicher aufgenommen wird.
Nach Abschluss wird die Funktion callback aufgerufen.
callback undefined übergeben: Das bedeutet, dass die Übersetzung erfolgreich war und über die Methode api.fetch abgerufen werden kann.callback ein error übergeben: Das bedeutet, dass etwas schiefgelaufen ist.
Die Weltraum-API ist instabil, das heißt, die API schlägt oft fehl.
Wenn das passiert, ist es in Ordnung, api.request erneut aufzurufen.api.request('majQa’', callback);
// => undefined
//
// later: the passed callback is called with undefined
// because it was successful.
⚠ Warnung! ⚠
Die API vollbringt ihre Magie, indem sie die verschiedenen Übersetzer herbeiteleportiert, wenn eine request eingeht.
Das ist eine sehr kostspielige Aktion, deshalb sollte sie nicht aufgerufen werden, wenn eine Übersetzung verfügbar ist.
Leider liest nicht jeder das Handbuch, deshalb gibt es ein System, um schlechte Akteure rauszuwerfen.
Wenn ein api.request für einen text aufgerufen wird, der verfügbar ist, wirft die API für diesen Aufruf einen AbusiveClientError, und für jeden weiteren Aufruf danach.
Stelle sicher, dass du niemals eine Übersetzung anforderst, wenn schon etwas übersetzt wurde.
Der kostenlose Dienst bietet nur Übersetzungen, die sich gerade im API-Speicher befinden.
Implementiere eine Methode free(text), die kostenlosen Mitgliedern Übersetzungen liefert, die bereits im API-Speicher vorhanden sind.
Ignoriere die Qualität und reiche alle von der API geworfenen Fehler weiter.
api.fetch (api.fetch gibt ein promise zurück)service.free('jIyaj');
// => Promise<...> resolves "I understand."
service.free("jIyajbe'");
// => Promise<...> rejects Error("Not yet translated")
Implementiere eine Methode batch([text, text, ...]) für kostenlose Mitglieder, die ein Array von Texten mit dem kostenlosen Dienst übersetzt und entweder alle Übersetzungen oder einen einzelnen Fehler zurückgibt.
BatchIsEmpty-Fehler ab, wenn keine Texte angegeben werdenservice.batch(['jIyaj', "majQa'"]);
// => Promise<...> resolves ["I understand.", "Well done!"]
service.batch(['jIyaj', "jIyajbe'"]);
// => Promise<...> rejects new Error("Not yet translated")
service.batch([]);
// => Promise<...> rejects BatchIsEmpty()
Implementiere eine Premium-Methode request(text), die anfordert, dass eine Übersetzung zum API-Speicher hinzugefügt wird.
Die Anfrage sollte bei einem Fehler automatisch erneut versucht werden.
Sie sollte nicht mehr als 3 Aufrufe für dieselbe Anfrage ausführen (ärgere die Weltraumübersetzer nicht!!!).
api.request keinen Fehler zurückgibt, löse mit undefined aufapi.request einen Fehler zurückgibt, versuche es höchstens zweimal erneutservice.request("jIyajbe'");
// => Promise<...> resolves (with nothing), can now be retrieved using the fetch API
Implementiere eine Premium-Methode premium(text, quality), um eine Übersetzung abzurufen.
Wenn eine Übersetzung NotAvailable ist, fordere die Übersetzung an und rufe sie ab, nachdem sie zum API-Speicher hinzugefügt wurde.
Die Methode sollte die Übersetzung nur zurückgeben, wenn sie einen bestimmten quality-Schwellenwert erfüllt.
api.fetch auflöst, prüfe die Qualität, bevor du auflöstapi.fetch ablehnt, fordere stattdessen die Übersetzung an
api.request ablehnt, reiche den Fehler weiterservice.premium("jIyajbe'", 100);
// => Promise<...> resolves "I don't understand."
service.premium("'arlogh Qoylu'pu'?", 100);
// => Promise<...> rejects QualityThresholdNotMet()
service.premium("'arlogh Qoylu'pu'?", 40);
// => Promise<...> resolves "What time is it?"
Hinweis
Die korrekte Übersetzung von 'arlogh Qoylu'pu'? ist How many times has it been heard?.
Melde dich bei Exercism an, um JavaScript mit 37 Konzepte159 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.