我已将代码重组为promises,并构建了一个很棒的长扁平承诺链,由多个.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?
});
}
答案 0 :(得分:339)
当您需要访问链中的中间值时,您应该将链条拆分成您需要的那些单件。而不是附加一个回调并以某种方式尝试多次使用其参数,将多个回调附加到同一个承诺 - 无论您需要结果值。别忘了,promise just represents (proxies) a future value!接下来,在线性链中从另一个派生一个promise,使用库提供给你的promise组合器来构建结果值。
这将导致非常简单的控制流程,清晰的功能组合,因此易于模块化。
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
});
}
在ES5中只有Promise.all
之后的回调中的参数解构,而不是ES5中的then
调用将由许多promise库提供的漂亮帮助器方法替换{{1>}。 {3}},Q,Bluebird,...):.spread(function(resultA, resultB) { …
。
Bluebird还有一个专用的when,可以用更简单(更有效)的结构替换Promise.all
+ spread
组合:
…
return Promise.join(a, b, function(resultA, resultB) { … });
答案 1 :(得分:210)
当然,这个问题也得到了语言设计者的认可。他们做了很多工作,而async functions proposal终于进入了
您不再需要单个then
调用或回调函数,就像在异步函数中(在被调用时返回一个promise),您只需等待promises直接解析即可。它还具有任意控制结构,如条件,循环和try-catch-clauses,但为了方便起见,我们在这里不需要它们:
async function getExample() {
var resultA = await promiseA(…);
// some processing
var resultB = await promiseB(…);
// more processing
return // something using both resultA and resultB
}
在我们等待ES8的同时,我们已经使用了非常类似的语法。 ES6带有generator functions,允许在任意放置的yield
关键字中将执行分开。这些切片可以相互独立地运行,甚至是异步运行 - 而这正是我们在运行下一步之前等待承诺解析时所做的事情。
有专用的库(如co或task.js),但许多promise库都有辅助函数(Q,Bluebird,when,...当你给他们一个产生承诺的生成函数时,为你做this async step-by-step execution。
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中运行,也有一些浏览器(或它们的开发版)相对较早地支持生成器语法。
但是,如果您希望/需要向后兼容,则无法使用没有转换器的那些。当前工具支持生成器函数和异步函数,例如参见generators和async functions上的Babel文档。
然后,还有很多其他compile-to-JS languages
专门用于简化异步编程。它们通常使用类似于await
的语法(例如Iced CoffeeScript),但也有一些类似Haskell的do
符号(例如LatteJs,{{ 3}},monadic或PureScript)。
答案 2 :(得分:97)
将promises-for-later-needed-values分配给变量,然后通过同步检查获取它们的值。该示例使用bluebird的.value()
方法,但许多库提供了类似的方法。
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();
});
}
答案 3 :(得分:51)
使用闭包来维护变量的范围(在我们的例子中,成功的回调函数参数)是自然的JavaScript解决方案。有了promises,我们可以任意nest and flatten .then()
个回调 - 它们在语义上是等价的,除了内部的范围。
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(function(resultB) {
// more processing
return // something using both resultA and resultB;
});
});
}
当然,这是建立一个缩进金字塔。如果缩进过大,您仍然可以应用旧工具来对抗pyramid of doom:modularize,使用额外的命名函数,并在您不再需要变量时立即压缩承诺链。
从理论上讲,你总是可以避免两个以上的嵌套层次(通过使所有闭包显式化),在实践中使用尽可能多的合理。
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
};
}
您还可以使用这种partial application的辅助函数,例如来自Underscore / lodash的_.partial
或native .bind()
method,以进一步减少缩进:
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
}
答案 4 :(得分:48)
与嵌套回调类似,此技术依赖于闭包。然而,链条保持平稳 - 而不是仅传递最新结果,每一步都会传递一些状态对象。这些状态对象会累积先前操作的结果,再次传递稍后将需要的所有值以及当前任务的结果。
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提供解构之前,许多承诺库(Q,Bluebird,when,...)提供了一个名为.spread()
的漂亮助手方法。它需要一个具有多个参数的函数 - 每个数组元素一个 - 用作.spread(function(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
来生成数组的承诺:
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
});
}
您可能不仅使用数组,而且使用任意复杂的对象。例如,在不同的辅助函数中使用_.extend
或Object.assign
:
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
});
}
虽然这种模式保证了扁平链条,而显式状态对象可以提高清晰度,但对于长链来说,这将变得乏味。特别是当您偶尔需要状态时,您仍然必须通过每一步。通过这个固定的接口,链中的单个回调相互紧密耦合,并且不灵活。它使得单个步骤的分解变得更加困难,并且不能直接从其他模块提供回调 - 它们总是需要包含在关注状态的样板代码中。像上面这样的抽象辅助函数可以缓解疼痛,但它总会存在。
答案 5 :(得分:32)
琐碎(但不优雅且相当错误)的解决方案是只使用更高范围的变量(链中的所有回调都可以访问)并在获得它们时将结果值写入它们:
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
});
}
除了许多变量之外,还可以使用(最初为空)对象,在该对象上将结果存储为动态创建的属性。
此解决方案有几个缺点:
Bluebird库鼓励使用传递的对象,使用their bind()
method将上下文对象分配给promise链。它可以通过其他不可用的this
keyword从每个回调函数访问。虽然对象属性比变量更容易被检测到错别字,但模式非常聪明:
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
}
这种方法可以很容易地在不支持.bind的promise库中进行模拟(虽然它的表达方式有点冗长,不能在表达式中使用):
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));
}
答案 6 :(得分:9)
使用本地范围的对象来收集承诺链中的中间结果是您提出的问题的合理方法。请考虑以下代码段:
function getExample(){
//locally scoped
const results = {};
return promiseA(...).then(function(resultA){
results.a = resultA;
return promiseB(...);
}).then(function(resultB){
results.b = resultB;
return promiseC(...);
}).then(function(resultC){
//Resolve with composite of all promises
return Promise.resolve(results.a + results.b + resultC);
}).catch(function(error){
return Promise.reject(error);
});
}
答案 7 :(得分:7)
节点7.4现在支持带有和声标志的async / await调用。
试试这个:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
并使用以下命令运行文件:
node --harmony-async-await getExample.js
简单可以!
答案 8 :(得分:5)
另一个答案,使用babel-node
版本< 6
使用async - await
npm install -g babel@5.6.14
example.js:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
然后,运行babel-node example.js
并瞧!
答案 9 :(得分:5)
这一天,我也遇到了一些像你这样的问题。最后,我找到了问题的一个很好的解决方案,它简单易读。我希望这可以帮到你。
根据how-to-chain-javascript-promises
好的,让我们看一下代码:
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);
});
答案 10 :(得分:2)
另一个答案,使用顺序执行器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');
})
&#13;
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
&#13;
答案 11 :(得分:1)
使用bluebird时,您可以使用.bind
方法在promise链中共享变量:
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;
});
请查看此链接以获取更多信息:
答案 12 :(得分:1)
我认为你可以使用RSVP的哈希值。
如下所示:
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);
});
答案 13 :(得分:1)
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?
});
}
简单方法:D
答案 14 :(得分:1)
我不会在我自己的代码中使用这种模式,因为我不是使用全局变量的忠实粉丝。然而,在紧要关头它会起作用。
用户是一个有道理的Mongoose模型。
var globalVar = '';
User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});
答案 15 :(得分:0)
解决方案:
您可以使用“ bind”将中间值显式地放置在任何后续“ then”函数的作用域中。这是一个很好的解决方案,不需要更改Promises的工作方式,只需要一行或两行代码即可传播值,就像已经传播了错误一样。
这是一个完整的示例:
// 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);
(注意:已经测试了此解决方案的更复杂和完整的版本,但未测试此示例版本,因此可能存在错误。)
答案 16 :(得分:-1)
我了解到的Promise是仅将其用作返回值避免引用它们(如果可能)。异步/等待语法对此特别实用。如今,所有最新的浏览器和节点都支持它:https://caniuse.com/#feat=async-functions,这是一种简单的行为,其代码就像读取同步代码一样,而忘记了回调...
在我确实需要引用的情况下,诺言是在独立/无关的地方进行创建和解决的情况。因此,为了代替“遥远的”承诺,我改为将其作为Deferred公开,而不是人为的关联,可能还有事件侦听器,以下代码在有效的es5中实现了它
/**
* 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;
}());
从我的打字稿项目中编译下来:
对于更复杂的情况,我经常使用这些家伙小承诺工具,而无需测试和键入依赖项。 p-map已经有用了好几次了。我认为他涵盖了大多数用例:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=