Ich habe meinen Code in Versprechen umstrukturiert und eine wunderbare lange flache Versprechen-Kette aufgebaut, die aus mehreren .then()
besteht. Rückrufe. Am Ende möchte ich einen zusammengesetzten Wert zurückgeben und muss auf mehrere Zwischenversprechungsergebnisse zugreifen . Die Auflösungswerte aus der Mitte der Sequenz sind im letzten Rückruf jedoch nicht gültig. Wie kann ich darauf zugreifen?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
Natürlich wurde dieses Problem auch von den Sprachdesignern erkannt. Sie haben viel Arbeit geleistet und der Vorschlag Async-Funktionen hat es endlich geschafft
Sie benötigen keine einzige then
Aufruf- oder Rückruffunktion mehr, da Sie in einer asynchronen Funktion (die beim Aufrufen ein Versprechen zurückgibt) einfach darauf warten können, dass die Versprechen direkt aufgelöst werden. Es enthält auch beliebige Kontrollstrukturen wie Bedingungen, Schleifen und Try-Catch-Klauseln, die wir hier jedoch aus Gründen der Bequemlichkeit nicht benötigen:
async function getExample() {
var resultA = await promiseA(…);
// some processing
var resultB = await promiseB(…);
// more processing
return // something using both resultA and resultB
}
Während wir auf ES8 warteten, verwendeten wir bereits eine sehr ähnliche Syntax. ES6 wurde mit Generatorfunktionen ausgeliefert, mit denen die Ausführung an beliebig platzierten yield
Schlüsselwörtern aufgeteilt werden kann. Diese Slices können unabhängig voneinander oder sogar asynchron nacheinander ausgeführt werden - und genau das tun wir, wenn wir auf eine versprochene Lösung warten möchten, bevor wir den nächsten Schritt ausführen.
Es gibt dedizierte Bibliotheken (wie co oder task.js ), aber auch viele vielversprechende Bibliotheken haben Hilfsfunktionen ( Q , Bluebird , wann ,…) die diese asynchrone schrittweise Ausführung für Sie tun, wenn Sie ihnen eine Generatorfunktion geben, die Versprechen gibt.
var getExample = Promise.coroutine(function* () {
// ^^^^^^^^^^^^^^^^^ Bluebird syntax
var resultA = yield promiseA(…);
// some processing
var resultB = yield promiseB(…);
// more processing
return // something using both resultA and resultB
});
Dies funktionierte in Node.js seit Version 4.0, und auch einige Browser (oder ihre Entwicklungseditionen) haben die Generatorsyntax relativ früh unterstützt.
Wenn Sie jedoch abwärtskompatibel sein möchten/müssen, können Sie diese nicht ohne Transpiler verwenden. Sowohl Generatorfunktionen als auch Async-Funktionen werden von den aktuellen Tools unterstützt, siehe zum Beispiel die Dokumentation von Babel zu Generatoren und Async-Funktionen .
Und dann gibt es noch viele andere in JS-Sprachen kompilieren , die der Erleichterung der asynchronen Programmierung gewidmet sind. Sie verwenden normalerweise eine ähnliche Syntax wie await
(z. B. Iced CoffeeScript ), aber es gibt auch andere, die eine Haskell-ähnliche do
- -Notation aufweisen (z. B. LatteJs , monadisch , PureScript oder LispyScript ).
Wenn Sie auf die Zwischenwerte in Ihrer Kette zugreifen müssen, sollten Sie Ihre Kette in die einzelnen Teile aufteilen, die Sie benötigen. Anstatt einen Rückruf anzuhängen und irgendwie zu versuchen, seinen Parameter mehrmals zu verwenden, hängen Sie mehrere Rückrufe an dasselbe Versprechen an - wo immer Sie den Ergebniswert benötigen. Vergiss nicht, ein Versprechen repräsentiert nur einen zukünftigen Wert ! Verwenden Sie neben der Ableitung eines Versprechens von dem anderen in einer linearen Kette die Versprechungskombinatoren, die Sie von Ihrer Bibliothek erhalten, um den Ergebniswert zu erstellen.
Dies führt zu einem sehr einfachen Kontrollfluss, einer klaren Zusammenstellung der Funktionen und damit zu einer einfachen Modularisierung.
function getExample() {
var a = promiseA(…);
var b = a.then(function(resultA) {
// some processing
return promiseB(…);
});
return Promise.all([a, b]).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
Anstelle der Parameterdestrukturierung im Callback nach Promise.all
, die erst mit ES6 verfügbar wurde, wurde in ES5 der then
-Aufruf durch eine raffinierte Hilfsmethode ersetzt, die von vielen vielversprechenden Bibliotheken bereitgestellt wurde ( Q , Bluebird , wenn ,…): .spread(function(resultA, resultB) { …
.
Bluebird bietet auch eine dedizierte join
-Funktion , um diese Promise.all
+ spread
-Kombination durch ein einfacheres (und effizienteres) Konstrukt zu ersetzen:
…
return Promise.join(a, b, function(resultA, resultB) { … });
Zuweisen von Versprechungen für später benötigte Werte zu Variablen und anschließendes Abrufen ihres Werts durch synchrone Überprüfung. In diesem Beispiel wird die .value()
-Methode von bluebird verwendet, aber viele Bibliotheken bieten ähnliche Methoden an.
function getExample() {
var a = promiseA(…);
return a.then(function() {
// some processing
return promiseB(…);
}).then(function(resultB) {
// a is guaranteed to be fulfilled here so we can just retrieve its
// value synchronously
var aValue = a.value();
});
}
Dies kann für beliebig viele Werte verwendet werden:
function getExample() {
var a = promiseA(…);
var b = a.then(function() {
return promiseB(…)
});
var c = b.then(function() {
return promiseC(…);
});
var d = c.then(function() {
return promiseD(…);
});
return d.then(function() {
return a.value() + b.value() + c.value() + d.value();
});
}
Die Verwendung von Closures zur Beibehaltung des Gültigkeitsbereichs von Variablen (in unserem Fall der Parameter der Success Callback-Funktion) ist die natürliche JavaScript-Lösung. Mit Versprechungen können wir willkürlich Rückrufe verschachteln und abflachen.then()
- sie sind semantisch äquivalent, mit Ausnahme des Umfangs des inneren.
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(function(resultB) {
// more processing
return // something using both resultA and resultB;
});
});
}
Natürlich baut dies eine Einkerbungspyramide. Wenn die Einrückung zu groß wird, können Sie immer noch die alten Werkzeuge anwenden, um der Pyramide des Schicksals entgegenzuwirken : Modularisieren, zusätzliche benannte Funktionen verwenden und das Versprechen reduzieren verketten, sobald Sie keine Variable mehr benötigen.
Theoretisch können Sie immer mehr als zwei Verschachtelungsebenen vermeiden (indem Sie alle Verschlüsse explizit angeben). In der Praxis werden so viele verwendet, wie angemessen sind.
function getExample() {
// preprocessing
return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
return function(resultA) {
// some processing
return promiseB(…).then(makeBhandler(resultA, …));
};
}
function makeBhandler(resultA, …) {
return function(resultB) {
// more processing
return // anything that uses the variables in scope
};
}
Sie können für diese Art von Teilanwendung auch Hilfsfunktionen verwenden, wie _.partial
from Underscore / lodash oder die native .bind()
Methode , um die Einrückung weiter zu verringern:
function getExample() {
// preprocessing
return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
// some processing
return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
// more processing
return // anything that uses resultA and resultB
}
Ähnlich wie beim Verschachteln der Rückrufe stützt sich diese Technik auf Verschlüsse. Die Kette bleibt jedoch flach - anstatt nur das neueste Ergebnis zu übergeben, wird bei jedem Schritt ein Statusobjekt übergeben. Diese Statusobjekte akkumulieren die Ergebnisse der vorherigen Aktionen und geben alle Werte an, die später erneut benötigt werden, sowie das Ergebnis der aktuellen Aufgabe.
_function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
}).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
_
Hier ist der kleine Pfeil _b => [resultA, b]
_ die Funktion, die resultA
schließt und ein Array beider Ergebnisse an den nächsten Schritt übergibt. Wobei die Syntax der Parameter-Destrukturierung verwendet wird, um sie wieder in einzelne Variablen aufzuteilen.
Bevor die Destrukturierung mit ES6 verfügbar wurde, wurde von vielen vielversprechenden Bibliotheken eine nützliche Hilfsmethode namens .spread()
bereitgestellt ( Q , Bluebird , when ,…). Eine Funktion mit mehreren Parametern - einer für jedes Array-Element - wird als .spread(function(resultA, resultB) { …
verwendet.
Natürlich kann dieser hier erforderliche Verschluss durch einige Hilfsfunktionen weiter vereinfacht werden, z.
_function addTo(x) {
// imagine complex `arguments` fiddling or anything that helps usability
// but you get the idea with this simple one:
return res => [x, res];
}
…
return promiseB(…).then(addTo(resultA));
_
Alternativ können Sie _Promise.all
_ verwenden, um das Versprechen für das Array zu erstellen:
_function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
// as if passed to Promise.resolve()
}).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
_
Und Sie verwenden möglicherweise nicht nur Arrays, sondern auch beliebig komplexe Objekte. Zum Beispiel mit _.extend
oder Object.assign
in einer anderen Hilfsfunktion:
_function augment(obj, name) {
return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(augment({resultA}, "resultB"));
}).then(function(obj) {
// more processing
return // something using both obj.resultA and obj.resultB
});
}
_
Während dieses Muster eine flache Kette garantiert und explizite Statusobjekte die Übersichtlichkeit verbessern können, wird es für eine lange Kette mühsam. Besonders wenn Sie den Staat nur sporadisch brauchen, müssen Sie ihn noch jeden Schritt durchlaufen. Mit dieser festen Schnittstelle sind die einzelnen Rückrufe in der Kette ziemlich eng gekoppelt und können nicht geändert werden. Es erschwert das Ausklammern einzelner Schritte, und Rückrufe können nicht direkt von anderen Modulen geliefert werden. Sie müssen immer in einen Code eingebettet sein, der den Status berücksichtigt. Abstrakte Hilfsfunktionen wie die oben genannten können den Schmerz etwas lindern, sind aber immer vorhanden.
Die triviale (aber unelegante und ziemlich fehleranfällige) Lösung besteht darin, einfach Variablen mit höherem Gültigkeitsbereich (auf die alle Rückrufe in der Kette zugreifen können) zu verwenden und Ergebniswerte zu schreiben, wenn Sie diese erhalten:
function getExample() {
var resultA;
return promiseA(…).then(function(_resultA) {
resultA = _resultA;
// some processing
return promiseB(…);
}).then(function(resultB) {
// more processing
return // something using both resultA and resultB
});
}
Anstelle vieler Variablen kann man auch ein (anfangs leeres) Objekt verwenden, auf dem die Ergebnisse als dynamisch erzeugte Eigenschaften gespeichert werden.
Diese Lösung hat mehrere Nachteile:
Die Bluebird-Bibliothek empfiehlt die Verwendung eines weitergegebenen Objekts mit ihrer bind()
-Methode , um ein Kontextobjekt einer Versprechen-Kette zuzuweisen. Der Zugriff auf die einzelnen Rückruffunktionen erfolgt über das ansonsten nicht verwendbare this
keyword . Während Objekteigenschaften anfälliger für unerkannte Tippfehler sind als Variablen, ist das Muster ziemlich clever:
function getExample() {
return promiseA(…)
.bind({}) // Bluebird only!
.then(function(resultA) {
this.resultA = resultA;
// some processing
return promiseB(…);
}).then(function(resultB) {
// more processing
return // something using both this.resultA and resultB
}).bind(); // don't forget to unbind the object if you don't want the
// caller to access it
}
Dieser Ansatz kann leicht in Versprechungsbibliotheken simuliert werden, die .bind nicht unterstützen (wenn auch etwas ausführlicher und nicht in einem Ausdruck verwendbar):
function getExample() {
var ctx = {};
return promiseA(…)
.then(function(resultA) {
this.resultA = resultA;
// some processing
return promiseB(…);
}.bind(ctx)).then(function(resultB) {
// more processing
return // something using both this.resultA and resultB
}.bind(ctx));
}
Die Verwendung eines Objekts mit lokalem Geltungsbereich zum Sammeln der Zwischenergebnisse in einer Versprechen-Kette ist eine vernünftige Herangehensweise an die von Ihnen gestellte Frage. Betrachten Sie das folgende Snippet:
function getExample(){
//locally scoped
const results = {};
return promiseA(paramsA).then(function(resultA){
results.a = resultA;
return promiseB(paramsB);
}).then(function(resultB){
results.b = resultB;
return promiseC(paramsC);
}).then(function(resultC){
//Resolve with composite of all promises
return Promise.resolve(results.a + results.b + resultC);
}).catch(function(error){
return Promise.reject(error);
});
}
Knoten 7.4 unterstützt jetzt Async/Warten-Anrufe mit dem Harmony-Flag.
Versuche dies:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
und starte die Datei mit:
node --harmony-async-await getExample.js
So einfach kann es sein!
In diesen Tagen habe ich auch einige Fragen wie Sie getroffen. Endlich finde ich eine gute Lösung mit der Frage, es ist einfach und gut zu lesen. Ich hoffe das kann dir helfen.
Nach How-to-Chain-Javascript-Versprechen
ok, schauen wir uns den Code an:
const firstPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('first promise is completed');
resolve({data: '123'});
}, 2000);
});
};
const secondPromise = (someStuff) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('second promise is completed');
resolve({newData: `${someStuff.data} some more data`});
}, 2000);
});
};
const thirdPromise = (someStuff) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('third promise is completed');
resolve({result: someStuff});
}, 2000);
});
};
firstPromise()
.then(secondPromise)
.then(thirdPromise)
.then(data => {
console.log(data);
});
Eine andere Antwort mit babel-node
Version <6
async - await
verwenden
npm install -g [email protected]
example.js:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
Dann starte babel-node example.js
und voila!
Eine andere Antwort mit sequentiellem Executor nsynjs :
function getExample(){
var response1 = returnPromise1().data;
// promise1 is resolved at this point, '.data' has the result from resolve(result)
var response2 = returnPromise2().data;
// promise2 is resolved at this point, '.data' has the result from resolve(result)
console.log(response, response2);
}
nynjs.run(getExample,{},function(){
console.log('all done');
})
function synchronousCode() {
var urls=[
"https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
"https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
];
for(var i=0; i<urls.length; i++) {
var len=window.fetch(urls[i]).data.text().data.length;
// ^ ^
// | +- 2-nd promise result
// | assigned to 'data'
// |
// +-- 1-st promise result assigned to 'data'
//
console.log('URL #'+i+' : '+urls[i]+", length: "+len);
}
}
nsynjs.run(synchronousCode,{},function(){
console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
Ich werde dieses Muster nicht in meinem eigenen Code verwenden, da ich kein großer Fan von globalen Variablen bin. Zur Not wird es jedoch funktionieren.
Benutzer ist ein vielversprechendes Mungomodell.
var globalVar = '';
User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});
Wenn Sie Bluebird verwenden, können Sie die .bind
-Methode verwenden, um Variablen in der Versprechen-Kette zu teilen:
somethingAsync().bind({})
.spread(function (aValue, bValue) {
this.aValue = aValue;
this.bValue = bValue;
return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
return this.aValue + this.bValue + cValue;
});
bitte überprüfen Sie diesen Link für weitere Informationen:
function getExample() {
var retA, retB;
return promiseA(…).then(function(resultA) {
retA = resultA;
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
//retA is value of promiseA
return // How do I gain access to resultA here?
});
}
einfacher Weg: D
Ich denke, Sie können RSVP-Hash verwenden.
Sowas wie unten:
const mainPromise = () => {
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('first promise is completed');
resolve({data: '123'});
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('second promise is completed');
resolve({data: '456'});
}, 2000);
});
return new RSVP.hash({
prom1: promise1,
prom2: promise2
});
};
mainPromise()
.then(data => {
console.log(data.prom1);
console.log(data.prom2);
});
Lösung:
Sie können Zwischenwerte in den Gültigkeitsbereich einer späteren 'then'-Funktion explizit einfügen, indem Sie' bind 'verwenden. Es ist eine gute Lösung, bei der die Funktionsweise von Promises nicht geändert werden muss. Es sind nur ein oder zwei Zeilen Code erforderlich, um die Werte weiterzugeben, genau wie Fehler bereits weitergegeben werden.
Hier ist ein vollständiges Beispiel:
// Get info asynchronously from a server
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
{
var scope={localInfo:localInfo}; // Create an explicit scope object
var thenFunc=p2.bind(scope); // Create a temporary function with this scope
return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
} // pLogInfo
// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
{
// Do the final 'then' in the chain: Writes "local info, server info"
return pWriteFile('log',this.localInfo+','+serverInfo);
} // p2
Diese Lösung kann wie folgt aufgerufen werden:
pLogInfo("local info").then().catch(err);
(Hinweis: Es wurde eine komplexere und vollständigere Version dieser Lösung getestet, jedoch nicht diese Beispielversion, sodass ein Fehler auftreten kann.)