Javascript 如何访问.then()链中以前的承诺结果?
我将我的代码重组为,并构建了一个奇妙的长平面承诺链,由多个Javascript 如何访问.then()链中以前的承诺结果?,javascript,scope,promise,bluebird,es6-promise,Javascript,Scope,Promise,Bluebird,Es6 Promise,我将我的代码重组为,并构建了一个奇妙的长平面承诺链,由多个.then()回调组成。最后,我想返回一些复合值,并需要访问多个中间承诺结果。但是,序列中间的分辨率值不在上一次回调的范围内,如何访问它们 function getExample() { return promiseA(…).then(function(resultA) { // Some processing return promiseB(…); }).then(function(res
.then()
回调组成。最后,我想返回一些复合值,并需要访问多个中间承诺结果。但是,序列中间的分辨率值不在上一次回调的范围内,如何访问它们
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?
});
}
嵌套(和)闭包
使用闭包来维护变量的作用域(在我们的例子中是success回调函数参数)是自然的JavaScript解决方案。有了承诺,我们可以任意.then()
回调-它们在语义上是等价的,除了内部回调的作用域
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(function(resultB) {
// more processing
return // something using both resultA and resultB;
});
});
}
当然,这是建立一个缩进金字塔。如果缩进变得太大,您仍然可以应用旧的工具来解决这个问题:模块化,使用额外命名的函数,并在不再需要变量时立即展平承诺链。理论上,您总是可以避免两级以上的嵌套(通过明确所有闭包),在实践中尽可能多地使用合理的嵌套
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
};
}
您还可以对此类缩进使用辅助函数,如从/或的\uu.partial
,以进一步减少缩进:
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
}
显式传递
与嵌套回调类似,这种技术依赖于闭包。然而,链保持平坦-不是只传递最新的结果,而是为每个步骤传递一些状态对象。这些状态对象累积以前操作的结果,将以后需要的所有值再加上当前任务的结果进行传递
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
});
}
在这里,这个小箭头b=>[resultA,b]
是关闭resultA
的函数,它将两个结果的数组传递给下一步。它使用参数解构语法将其再次分解为单个变量
在ES6提供解构之前,许多promise库(、、…)支持一种称为.spread()
的漂亮助手方法。它使用一个具有多个参数的函数(每个数组元素一个参数)作为.spread(函数(resultA,resultB){…
)
当然,这里需要的闭包可以通过一些辅助函数进一步简化,例如
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));
或者,您可以使用Promise.all
为数组生成Promise:
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
});
}
您可能不仅使用数组,还可以使用任意复杂的对象。例如,使用或在不同的辅助函数中:
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
});
}
虽然此模式保证了平面链,并且显式状态对象可以提高清晰度,但对于长链来说,它将变得单调乏味。特别是当您偶尔需要状态时,您仍然必须将其贯穿每一步。有了此固定接口,链中的单个回调非常紧密地耦合在一起,并且无法灵活地进行更改。这使得分解单个步骤变得更加困难,并且不能直接从其他模块提供回调-它们总是需要包装在关心状态的样板代码中。像上面这样的抽象帮助函数可以减轻一些痛苦,但它总是存在的。ECMAScript Harmony
当然,语言设计者也认识到了这个问题。他们做了大量的工作,最终将其转化为
ECMAScript 8
您不再需要像在异步函数(在被调用时返回承诺)中那样的单个then
调用或回调函数您只需等待承诺直接解析即可。它还具有任意控制结构,如条件、循环和try-catch子句,但为了方便起见,此处不需要它们:
async function getExample() {
var resultA = await promiseA(…);
// some processing
var resultB = await promiseB(…);
// more processing
return // something using both resultA and resultB
}
ECMAScript 6
当我们在等待ES8时,我们已经使用了一种非常类似的语法。ES6附带了一种语法,它允许以任意放置的yield
关键字将执行拆分为多个部分。这些部分可以相互独立地运行,甚至异步地运行——这正是我们在等待承诺解决时所做的在运行下一步之前,请执行以下操作
有专门的库(如或),但也有许多promise库具有帮助函数(、…),当您为它们提供生成承诺的生成器函数时,它们可以为您提供帮助
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
});
自版本4.0以来,Node.js中确实可以使用这种语法,而且一些浏览器(或其开发版本)相对较早地支持生成器语法
ECMAScript 5
但是,如果您希望/需要向后兼容,则不能在没有transpiler的情况下使用这些函数。当前工具支持生成器函数和异步函数,请参阅Babel on和的文档
然后,还有很多其他的
它们通常使用类似于await
(例如)的语法,但也有其他一些语法具有类似于do
-符号(例如,or)的Haskell特性
简单(但不雅观且容易出错)的解决方案是只使用范围更大的变量(链中的所有回调都可以访问该变量)并在获得结果值时将结果值写入它们:
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
});
}
也可以使用(最初为空)对象代替许多变量,在该对象上,结果作为动态创建的属性存储
此解决方案有几个缺点:
- ,及
- 这种模式不能跨函数边界工作,因为函数的声明不能离开共享作用域,所以函数更难实现模块化
- 变量的作用域不会阻止在初始化变量之前访问它们。这尤其适用于可能发生争用条件的复杂承诺构造(循环、分支、例外)。明确传递状态(承诺鼓励)会强制使用更干净的编码样式来防止这种情况
- 必须为这些共享变量选择范围
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 }
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)); }
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 }); }
… return Promise.join(a, b, function(resultA, resultB) { … });
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(); }); }
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(); }); }
var globalVar = ''; User.findAsync({}).then(function(users){ globalVar = users; }).then(function(){ console.log(globalVar); });
async function getExample(){ let response = await returnPromise(); let response2 = await returnPromise2(); console.log(response, response2) } getExample()
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; });
async function getExample(){ let response = await returnPromise(); let response2 = await returnPromise2(); console.log(response, response2) } getExample()
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? }); }
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); }); }
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'); })
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); });
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); });
// 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
pLogInfo("local info").then().catch(err);
/** * Promise like object that allows to resolve it promise from outside code. Example: * ``` class Api { fooReady = new Deferred<Data>() private knower() { inOtherMoment(data=>{ this.fooReady.resolve(data) }) } } ``` */ var Deferred = /** @class */ (function () { function Deferred(callback) { var instance = this; this.resolve = null; this.reject = null; this.status = 'pending'; this.promise = new Promise(function (resolve, reject) { instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); }; instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); }; }); if (typeof callback === 'function') { callback.call(this, this.resolve, this.reject); } } Deferred.prototype.then = function (resolve) { return this.promise.then(resolve); }; Deferred.prototype.catch = function (r) { return this.promise.catch(r); }; return Deferred; }());