Versprechen nicht nur Rückrufe?

429

Ich habe JavaScript seit ein paar Jahren entwickelt und verstehe die Aufregung um Versprechen überhaupt nicht.

Alles, was ich tue, scheint sich zu ändern:

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});

Wofür ich sowieso eine Bibliothek wie Async verwenden könnte , mit so etwas wie:

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});

Welches ist mehr Code und weniger lesbar. Ich habe hier nichts gewonnen, es ist auch nicht plötzlich magisch "flach". Ganz zu schweigen davon, Dinge in Versprechen umwandeln zu müssen.

Also, was ist die große Aufregung um Versprechen hier?

Benjamin Gruenbaum
quelle
11
Zum Thema : Es gibt einen wirklich informativen Artikel über Versprechen auf Html5Rocks: html5rocks.com/de/tutorials/es6/promises
ComFreek
2
Zu Ihrer Information, die Antwort, die Sie akzeptiert haben, ist dieselbe alte Liste der trivialen Vorteile, die überhaupt nicht der Punkt von Versprechungen sind und mich nicht einmal davon überzeugt haben, Versprechungen zu verwenden: /. Was mich überzeugt hat, Versprechen zu verwenden, war der DSL-Aspekt, wie in Oskars Antwort beschrieben
Esailija
@Esailija gut, dein Leet Speak hat mich überzeugt. Ich habe die andere Antwort akzeptiert, obwohl ich denke, dass Bergis eine einige wirklich gute (und andere) Punkte aufwirft.
Benjamin Gruenbaum
@Esailija "Was mich überzeugt hat, Versprechen zu verwenden, war der DSL-Aspekt, wie in Oskars Antwort beschrieben" << Was ist "DSL"? und auf welchen "DSL-Aspekt" beziehen Sie sich?
Montag,
1
@monsto: DSL: Domain Specific Language, eine Sprache, die speziell für die Verwendung in einer bestimmten Teilmenge eines Systems entwickelt wurde (z. B. SQL oder ORM, um mit der Datenbank zu kommunizieren, Regex, um Muster zu finden usw.). In diesem Zusammenhang ist "DSL" die API von Promise, die, wenn Sie Ihren Code so strukturieren, wie Oscar es getan hat, fast wie syntaktischer Zucker ist, der JavaScript ergänzt, um den speziellen Kontext asynchroner Operationen zu adressieren. Versprechen schaffen einige Redewendungen, die sie fast zu einer Sprache machen, die es dem Programmierer ermöglicht, den etwas schwer fassbaren mentalen Fluss dieser Art von Strukturen leichter zu erfassen.
Michael Ekoka

Antworten:

630

Versprechen sind keine Rückrufe. Ein Versprechen repräsentiert das zukünftige Ergebnis einer asynchronen Operation . Wenn Sie sie so schreiben, wie Sie es tun, erhalten Sie natürlich wenig Nutzen. Wenn Sie sie jedoch so schreiben, wie sie verwendet werden sollen, können Sie asynchronen Code so schreiben, dass er synchronem Code ähnelt und viel einfacher zu befolgen ist:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});

Sicherlich nicht viel weniger Code, aber viel besser lesbar.

Dies ist jedoch nicht das Ende. Lassen Sie uns die wahren Vorteile entdecken: Was ist, wenn Sie in einem der Schritte nach Fehlern suchen möchten? Es wäre die Hölle, es mit Rückrufen zu tun, aber mit Versprechungen ist ein Kinderspiel:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
});

So ziemlich das gleiche wie ein try { ... } catchBlock.

Noch besser:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
}).then(function() {
     //do something whether there was an error or not
     //like hiding an spinner if you were performing an AJAX request.
});

Und noch besser: Was passiert , wenn diese drei Anrufe api, api2, api3könnte gleichzeitig ausgeführt werden (zB wenn sie AJAX Anrufe) , aber man benötigt für die drei warten? Ohne Versprechen sollten Sie eine Art Zähler erstellen müssen. Mit Versprechungen ist die Verwendung der ES6-Notation ein weiteres Kinderspiel und ziemlich ordentlich:

Promise.all([api(), api2(), api3()]).then(function(result) {
    //do work. result is an array contains the values of the three fulfilled promises.
}).catch(function(error) {
    //handle the error. At least one of the promises rejected.
});

Ich hoffe, Sie sehen Versprechen jetzt in einem neuen Licht.

Oscar Paz
quelle
124
Sie hätten es wirklich nicht als "Versprechen" bezeichnen sollen. "Future" ist mindestens 100x besser.
Pacerier
12
@ Pacerier, weil Future nicht von jQuery befleckt wurde?
Esailija
5
Alternatives Muster (je nachdem, was gewünscht wird: api (). Dann (api2) .then (api3) .then (doWork); Das heißt, wenn api2 / api3-Funktionen Eingaben aus dem letzten Schritt übernehmen und selbst neue Versprechen zurückgeben, werden sie kann einfach ohne zusätzliche Umhüllung angekettet werden. Das heißt, sie komponieren.
Dtipson
1
Was ist, wenn in api2und asynchrone Operationen ausgeführt werden api3? Würde der letzte .thenerst aufgerufen, wenn diese asynchronen Vorgänge abgeschlossen sind?
NiCk Newman
8
Warum hast du mich markiert? Ich habe nur die Grammatik ein bisschen korrigiert. Ich bin kein JS-Experte. :)
Scott Arciszewski
169

Ja, Versprechen sind asynchrone Rückrufe. Sie können nichts tun, was Rückrufe nicht können, und Sie haben mit der Asynchronität dieselben Probleme wie mit einfachen Rückrufen.

Versprechen sind jedoch mehr als nur Rückrufe. Sie sind eine sehr mächtige Abstraktion, ermöglichen saubereren und besseren Funktionscode mit weniger fehleranfälligem Boilerplate.

Was ist die Hauptidee?

Versprechen sind Objekte, die das Ergebnis einer einzelnen (asynchronen) Berechnung darstellen. Sie lösen dieses Ergebnis nur einmal auf. Es gibt ein paar Dinge, was dies bedeutet:

Versprechen implementieren ein Beobachtermuster:

  • Sie müssen die Rückrufe, die den Wert verwenden, nicht kennen, bevor die Aufgabe abgeschlossen ist.
  • Anstatt Rückrufe als Argumente für Ihre Funktionen zu erwarten, können Sie ganz einfach returnein Promise-Objekt erstellen
  • Das Versprechen speichert den Wert und Sie können jederzeit transparent einen Rückruf hinzufügen. Es wird aufgerufen, wenn das Ergebnis verfügbar ist. "Transparenz" bedeutet, dass wenn Sie ein Versprechen haben und einen Rückruf hinzufügen, es für Ihren Code keinen Unterschied macht, ob das Ergebnis bereits eingetroffen ist - die API und die Verträge sind identisch, was das Zwischenspeichern / Auswendiglernen erheblich vereinfacht.
  • Sie können problemlos mehrere Rückrufe hinzufügen

Versprechen sind verkettbar ( monadisch , wenn Sie wollen ):

  • Wenn Sie den Wert zu transformieren müssen , dass ein Versprechen dar, Sie Karte eine Funktion über das Versprechen verwandeln und ein neues Versprechen zurück, die das transformierte Ergebnis darstellt. Sie können den Wert nicht synchron dazu bringen, ihn irgendwie zu verwenden, aber Sie können die Transformation im Versprechenskontext leicht aufheben . Keine Rückrufe.
  • Wenn Sie zwei asynchrone Aufgaben verketten möchten, können Sie die .then()Methode verwenden. Es wird ein Rückruf benötigt, um mit dem ersten Ergebnis aufgerufen zu werden, und es wird ein Versprechen für das Ergebnis des Versprechens zurückgegeben, dass der Rückruf zurückkehrt.

Klingt kompliziert? Zeit für ein Codebeispiel.

var p1 = api1(); // returning a promise
var p3 = p1.then(function(api1Result) {
    var p2 = api2(); // returning a promise
    return p2; // The result of p2 …
}); // … becomes the result of p3

// So it does not make a difference whether you write
api1().then(function(api1Result) {
    return api2().then(console.log)
})
// or the flattened version
api1().then(function(api1Result) {
    return api2();
}).then(console.log)

Das Abflachen ist nicht magisch, aber Sie können es leicht tun. Für Ihr stark verschachteltes Beispiel wäre das (nahe) Äquivalent

api1().then(api2).then(api3).then(/* do-work-callback */);

Wenn das Anzeigen des Codes dieser Methoden zum Verständnis beiträgt, finden Sie hier ein paar grundlegende Versprechen in wenigen Zeilen .

Was ist die große Aufregung um Versprechen?

Die Promise-Abstraktion ermöglicht eine viel bessere Zusammensetzbarkeit von Funktionen. Neben der thenVerkettung erstellt die allFunktion beispielsweise ein Versprechen für das kombinierte Ergebnis mehrerer parallel wartender Versprechen.

Last but not least kommen Versprechen mit integrierter Fehlerbehandlung. Das Ergebnis der Berechnung könnte sein, dass das Versprechen entweder mit einem Wert erfüllt oder mit einem Grund abgelehnt wird . Alle Kompositionsfunktionen behandeln dies automatisch und verbreiten Fehler in Versprechensketten, sodass Sie sich nicht überall explizit darum kümmern müssen - im Gegensatz zu einer einfachen Rückrufimplementierung. Am Ende können Sie einen dedizierten Fehlerrückruf für alle aufgetretenen Ausnahmen hinzufügen.

Ganz zu schweigen davon, Dinge in Versprechen umwandeln zu müssen.

Bei Bibliotheken mit guten Versprechen ist das eigentlich ziemlich trivial. Siehe Wie konvertiere ich eine vorhandene Rückruf-API in Versprechen?

Bergi
quelle
Hallo Bergi, hättest du dieser SO-Frage etwas Interessantes hinzuzufügen? stackoverflow.com/questions/22724883/…
Sebastien Lorber
1
@Sebastien: Ich weiß (noch) nicht viel über Scala, und ich konnte nur wiederholen, was Benjamin gesagt hat :-)
Bergi
3
Nur eine kleine Bemerkung: Sie können nicht verwenden .then(console.log), da console.log vom Konsolenkontext abhängt. Auf diese Weise wird ein unzulässiger Aufruffehler verursacht. Verwenden Sie console.log.bind(console)oder x => console.log(x), um den Kontext zu binden.
Tamas Hegedus
3
@hege_hegedus: Es gibt Umgebungen, in denen consoleMethoden bereits gebunden sind. Und natürlich habe ich nur gesagt, dass beide Verschachtelungen genau das gleiche Verhalten haben, nicht dass eine von ihnen funktionieren würde :-P
Bergi
1
Das war großartig. Das brauchte ich: weniger Code und mehr Interpretation. Vielen Dank.
Adam Patterson
21

Zusätzlich zu den bereits etablierten Antworten verwandeln sich Promises mit ES6-Pfeilfunktionen von einem bescheiden leuchtenden kleinen blauen Zwerg direkt in einen roten Riesen. Das ist im Begriff, in eine Supernova zu fallen:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))

Wie Oligofren betonte, benötigen Sie ohne Argumente zwischen API-Aufrufen überhaupt keine anonymen Wrapper-Funktionen:

api().then(api2).then(api3).then(r3 => console.log(r3))

Und schließlich, wenn Sie ein supermassereiches Schwarzes Loch erreichen möchten, können Sie auf Versprechen warten:

async function callApis() {
    let api1Result = await api();
    let api2Result = await api2(api1Result);
    let api3Result = await api3(api2Result);

    return api3Result;
}
John Weisz
quelle
9
"mit ES6-Pfeilfunktionen Versprechen verwandeln sich von einem bescheiden leuchtenden kleinen blauen Stern direkt in einen roten Riesen. Das steht kurz vor dem Zusammenbruch in eine Supernova" Übersetzung: Die Kombination von ES6-
Pfeilfunktionen
3
Das lässt Versprechen wie eine kosmische Katastrophe klingen, was ich nicht für Ihre Absicht halte.
Michael McGinnis
Wenn Sie die Argumente in den apiXMethoden nicht verwenden, können Sie die Pfeilfunktionen auch ganz überspringen : api().then(api2).then(api3).then(r3 => console.log(r3)).
Oligofren
@MichaelMcGinnis - Der positive Einfluss von Promises auf eine langweilige Rückrufhölle ist wie eine explodierende Supernova in einer dunklen Ecke des Weltraums.
John Weisz
Ich weiß, dass du es poetisch meinst, aber Versprechen sind ziemlich weit von "Supernova" entfernt. Es fällt mir ein, gegen das monadische Gesetz zu verstoßen oder keine Unterstützung für leistungsfähigere Anwendungsfälle wie die Stornierung oder die Rückgabe mehrerer Werte zu erhalten.
Dmitri Zaitsev
15

Zusätzlich zu den fantastischen Antworten oben können 2 weitere Punkte hinzugefügt werden:

1. Semantischer Unterschied:

Versprechen können bereits bei der Erstellung gelöst werden. Dies bedeutet, dass sie eher Bedingungen als Ereignisse garantieren . Wenn sie bereits aufgelöst sind, wird die an sie übergebene aufgelöste Funktion weiterhin aufgerufen.

Umgekehrt behandeln Rückrufe Ereignisse. Wenn das Ereignis, an dem Sie interessiert sind, vor der Registrierung des Rückrufs eingetreten ist, wird der Rückruf nicht aufgerufen.

2. Umkehrung der Kontrolle

Rückrufe beinhalten die Umkehrung der Kontrolle. Wenn Sie eine Rückruffunktion bei einer API registrieren, speichert die Javascript-Laufzeit die Rückruffunktion und ruft sie aus der Ereignisschleife auf, sobald sie zur Ausführung bereit ist.

Eine Erklärung finden Sie in der Javascript-Ereignisschleife .

Bei Promises liegt die Kontrolle beim aufrufenden Programm. Die .then () -Methode kann jederzeit aufgerufen werden, wenn wir das Versprechenobjekt speichern.

dww
quelle
1
Ich weiß nicht warum, aber das scheint eine bessere Antwort zu sein.
Radiantshaw
13

Zusätzlich zu den anderen Antworten fügt sich die ES2015-Syntax nahtlos in Versprechen ein und reduziert noch mehr Code auf dem Boilerplate:

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});
Duncan Luk
quelle
5

Versprechen sind keine Rückrufe, beide sind Programmiersprachen, die die asynchrone Programmierung erleichtern. Die Verwendung eines asynchronen / wartenden Programmierstils unter Verwendung von Coroutinen oder Generatoren, die Versprechen zurückgeben, könnte als dritte solche Redewendung angesehen werden. Ein Vergleich dieser Redewendungen in verschiedenen Programmiersprachen (einschließlich Javascript) finden Sie hier: https://github.com/KjellSchubert/promise-future-task

Kjell Schubert
quelle
3

Nein überhaupt nicht.

Rückrufe sind einfach Funktionen in JavaScript die aufgerufen und nach Abschluss der Ausführung einer anderen Funktion ausgeführt werden sollen. Wie passiert es?

In JavaScript werden Funktionen selbst als Objekte betrachtet, und daher können wie alle anderen Objekte auch Funktionen als Argumente an andere Funktionen gesendet werden . Der häufigste und allgemeinste Anwendungsfall, den man sich vorstellen kann, ist die Funktion setTimeout () in JavaScript.

Versprechen sind nichts anderes als ein viel improvisierterer Ansatz für die Behandlung und Strukturierung von asynchronem Code im Vergleich zu Rückrufen.

Das Versprechen erhält zwei Rückrufe in der Konstruktorfunktion: Auflösen und Ablehnen. Diese Rückrufe innerhalb von Versprechungen bieten uns eine differenzierte Kontrolle über Fehlerbehandlung und Erfolgsfälle. Der Rückruf zum Auflösen wird verwendet, wenn die Ausführung des Versprechens erfolgreich ausgeführt wurde, und der Rückruf zum Ablehnen wird verwendet, um die Fehlerfälle zu behandeln.

Ayush Jain
quelle
2

Keine Versprechen sind nur ein Wrapper für Rückrufe

Beispiel Sie können native Javascript-Versprechen mit Knoten js verwenden

my cloud 9 code link : https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
    request.get(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        resolve(body);
    }
    else {
        reject(error);
    }
    })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post id 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
})
.catch(function (e) {
    console.log(e);
})
.then(function (result) {
    res.end(result);
}
)

})


var server = app.listen(8081, function () {

var host = server.address().address
var port = server.address().port

console.log("Example app listening at http://%s:%s", host, port)

})


//run webservice on browser : http://localhost:8081/listAlbums
Apoorv
quelle
0

JavaScript-Versprechen verwenden tatsächlich Rückruffunktionen, um zu bestimmen, was zu tun ist, nachdem ein Versprechen aufgelöst oder abgelehnt wurde. Daher unterscheiden sich beide nicht grundlegend. Die Hauptidee hinter Promises besteht darin, Rückrufe entgegenzunehmen - insbesondere verschachtelte Rückrufe, bei denen Sie eine Art von Aktionen ausführen möchten, die jedoch besser lesbar sind.

Hamid Shoja
quelle
0

Verspricht Übersicht:

In JS können wir asynchrone Operationen (z. B. Datenbankaufrufe, AJAX-Aufrufe) in Versprechen einschließen. Normalerweise möchten wir eine zusätzliche Logik für die abgerufenen Daten ausführen. JS-Versprechen haben Handlerfunktionen, die das Ergebnis der asynchronen Operationen verarbeiten. Die Handlerfunktionen können sogar andere asynchrone Operationen enthalten, die sich auf den Wert der vorherigen asynchronen Operationen stützen können.

Ein Versprechen hat immer die 3 folgenden Zustände:

  1. ausstehend: Ausgangszustand jedes Versprechens, weder erfüllt noch abgelehnt.
  2. erfüllt: Der Vorgang erfolgreich abgeschlossen.
  3. abgelehnt: Der Vorgang ist fehlgeschlagen.

Ein ausstehendes Versprechen kann gelöst / erfüllt oder mit einem Wert abgelehnt werden. Dann werden die folgenden Handler-Methoden aufgerufen, die Rückrufe als Argumente verwenden:

  1. Promise.prototype.then() : Wenn das Versprechen aufgelöst ist, wird das Rückrufargument dieser Funktion aufgerufen.
  2. Promise.prototype.catch() : Wenn das Versprechen abgelehnt wird, wird das Rückrufargument dieser Funktion aufgerufen.

Obwohl die oben genannten Methoden Rückrufargumente erhalten, sind sie weit überlegen, als nur Rückrufe zu verwenden. Dies ist ein Beispiel, das viel verdeutlicht:

Beispiel

function createProm(resolveVal, rejectVal) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                console.log("Resolved");
                resolve(resolveVal);
            } else {
                console.log("Rejected");
                reject(rejectVal);
            }
        }, 1000);
    });
}

createProm(1, 2)
    .then((resVal) => {
        console.log(resVal);
        return resVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
        return resVal + 2;
    })
    .catch((rejectVal) => {
        console.log(rejectVal);
        return rejectVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
    })
    .finally(() => {
        console.log("Promise done");
    });

  • Die Funktion createProm erstellt Versprechen, die nach 1 Sekunde auf der Grundlage einer zufälligen Nr aufgelöst oder abgelehnt werden
  • Wenn das Versprechen aufgelöst wird, wird die erste thenMethode aufgerufen und der aufgelöste Wert als Argument für den Rückruf übergeben
  • Wenn das Versprechen abgelehnt wird, wird die erste catchMethode aufgerufen und der abgelehnte Wert als Argument übergeben
  • Die catchund thenMethoden geben Versprechen zurück, deshalb können wir sie verketten. Sie schließen jeden zurückgegebenen Wert Promise.resolveund jeden ausgelösten Wert (mit dem throwSchlüsselwort) ein Promise.reject. Jeder zurückgegebene Wert wird also in ein Versprechen umgewandelt, und für dieses Versprechen können wir erneut eine Handlerfunktion aufrufen.
  • Versprechensketten bieten uns eine genauere Kontrolle und einen besseren Überblick als verschachtelte Rückrufe. Beispielsweise behandelt die catchMethode alle Fehler, die vor dem catchHandler aufgetreten sind .
Willem van der Veen
quelle