我如何访问以前的Promise结果在一个。

我已将代码重构为承诺,并构建了一个精彩的长扁平承诺链,由多个.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?
});
}
251561 次浏览

嵌套(和)闭包

使用闭包来维护变量的范围(在我们的例子中,成功回调函数参数)是JavaScript的自然解决方案。使用Promise,我们可以任意巢和扁平.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
};
}

您还可以对这种部分应用使用辅助函数,例如下划线/豆沙中的_.partial原生.bind()方法,以进一步减少缩进:

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库(Q蓝鸟等)提供了一个名为.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为数组生成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
});
}

你可能不仅使用数组,还使用任意复杂的对象。例如,在不同的辅助函数中使用_.extendObject.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
});
}

虽然这种模式保证了平坦的链和显式的状态对象可以提高清晰度,但对于长链来说它会变得繁琐。特别是当你只零星地需要状态时,你仍然必须通过每一步来传递它。有了这个固定的接口,链中的单个回调是相当紧密耦合的,并且不能灵活地更改。它使得分解单个步骤变得更加困难,回调不能直接从其他模块提供——它们总是需要包裹在关心状态的样板代码中。像上面这样的抽象辅助函数可以缓解一点痛苦,但它将永远存在。

ECMAScript和谐

当然,这个问题也得到了语言设计者的认可。他们做了很多工作,异步函数提案最终成为

ECMAScript 8

你不再需要单个then调用或回调函数,因为在异步函数(在被调用时返回一个Promise)中,你可以简单地等待Promise直接解析。它还具有任意控制结构,如条件、循环和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解析时所做的。

有专门的库(如cotask.js),但也有许多承诺库有帮助函数(Q蓝鸟,…),当你给它们一个生成承诺的生成器函数时,它们会为你做这种异步一步一步的执行

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

但是,如果您想要/需要向后兼容,则不能使用没有转译器的那些。当前工具支持生成器函数和异步函数,例如,请参阅发电机异步函数上的Babel留档。

此外,还有许多其他的编译为JS语言 它们通常使用类似于await的语法(例如冰咖啡),但也有其他语法具有类似Haskell的do表示法(例如lattejs一元PureScriptLispyScript)。

可变上下文状态

简单(但不优雅且相当容易出错)的解决方案是只使用更高范围的变量(链中的所有回调都可以访问),并在获得结果值时将结果值写入它们:

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
});
}

除了许多变量之外,还可以使用一个(最初为空)对象,其结果存储为动态创建的属性。

这种解决方案有几个缺点:

  • 多变的国家是丑陋的全局变量是邪恶的
  • 这种模式不能跨函数边界工作,模块化函数更难,因为它们的声明不能离开共享范围
  • 变量的作用域并不妨碍在初始化之前访问它们。这尤其适用于可能发生竞争条件的复杂Promise构造(循环、分支、异常)。显式传递状态,一个承诺鼓励的声明式设计,强制采用更干净的编码风格来防止这种情况。
  • 必须正确选择这些共享变量的作用域。它需要是被执行函数的本地,以防止多个并行调用之间的竞争条件。例如,状态存储在实例上的情况就是这种情况。

Bluebird库鼓励使用传递的对象,使用bind()方法将上下文对象分配给Promise链。每个回调函数都可以通过否则无法使用的this关键字访问它。虽然对象属性比变量更容易出现未检测到的拼写错误,但模式非常聪明:

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));
}

打破锁链

当您需要访问链中的中间值时,您应该将链拆分为您需要的单个部分。与其附加一个回调并以某种方式尝试多次使用其参数,不如将多个回调附加到同一个承诺——无论您需要结果值的哪里。别忘了,一个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
});
}

Promise.all之后的回调中的参数解构仅在ES6中可用,而在ES5中,then调用将被许多Promise库(Q蓝鸟, …): .spread(function(resultA, resultB) { …提供的漂亮的helper方法替换。

Bluebird还具有专用的join函数,以更简单(更高效)的结构取代Promise.all+spread组合:

…
return Promise.join(a, b, function(resultA, resultB) { … });

同步检查

为变量分配Promise-for-postery-需要的值,然后通过同步检查获取它们的值。该示例使用蓝鸟的.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();
});
}

我不打算在自己的代码中使用这种模式,因为我不喜欢使用全局变量。但是,在紧要关头它会起作用。

用户是一个原始的猫鼬模型。

var globalVar = '';


User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});

另一个答案,使用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,瞧!

使用蓝鸟时,可以使用.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;
});

请查看此链接以获取更多信息:

Node 7.4现在支持带有和谐标志的async/wait调用。

试试这个:

async function getExample(){


let response = await returnPromise();


let response2 = await returnPromise2();


console.log(response, response2)


}


getExample()

并使用以下命令运行文件:

node --harmony-async-await getExample.js

尽可能简单!

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

对“可变上下文状态”的不太苛刻的旋转

使用本地范围的对象来收集Promise链中的中间结果是解决你提出的问题的合理方法。考虑以下片段:

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);
});
}
  • 全局变量是不好的,所以这个解决方案使用一个局部范围的变量,它不会造成伤害。它只能在函数内访问。
  • 可变状态是丑陋的,但这并不会以丑陋的方式突变状态。丑陋的可变状态传统上指的是修改函数参数或全局变量的状态,但这种方法只是修改了一个局部范围变量的状态,该变量的存在只是为了聚合Promise结果……一个一旦Promise解析就会死亡的变量。
  • 中间的Promise不会被阻止访问结果对象的状态,但这并没有引入一些可怕的场景,即链中的一个Promise会变质并破坏你的结果。在Promise的每个步骤中设置值的责任仅限于这个函数,总体结果要么是正确的,要么是不正确的……几年后在生产中不会出现一些bug(除非你有意!)
  • 这并没有引入并行调用会产生的竞争条件场景,因为每次调用get示例函数都会创建一个结果变量的新实例。

示例可在jsfiddle上获得

另一个答案,使用顺序执行器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>

这几天,我也遇到了一些像你这样的问题。最后,我找到了一个很好的解决方案,这个问题很简单,很容易阅读。我希望这能帮助你。

根据如何-到-链-JavaScript-承诺

好的,让我们看看代码:

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);
});

我认为你可以使用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);
});

解决方案:

通过使用bind,您可以将中间值显式地放在任何稍后的'然后'函数的范围内。这是一个很好的解决方案,不需要改变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);

(注意:此解决方案的更复杂和完整的版本已经过测试,但不是这个示例版本,因此它可能有一个bug。

我对Promise的了解是,如果可能,仅将其用作返回值避免提及它们。async/wait语法对此特别实用。今天所有最新的浏览器和节点都支持它:https://caniuse.com/#feat=async-functions,是一个简单的行为,代码就像阅读同步代码,忘记回调…

在我确实需要引用Promise的情况下,创建和解决发生在独立/不相关的地方。因此,为了解决“遥远”的Promise,我宁愿将Promise公开为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;
}());

转译成我的一个打字稿项目:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

对于更复杂的情况,我经常使用这些没有依赖关系的家伙小承诺实用程序测试和输入。p-map已经有用几次了。我认为他涵盖了大多数用例:

https://github.com/sindresorhus?utf8=%E2%9C%93&; tab=存储库&q=承诺&类型=源代码&语言=