我已将我的代码重组为promises,并构建了一个很棒的长 flat promise chain ,由多个 .then()
回调组成 . 最后我想返回一些复合值,并且需要访问多个 intermediate promise results . 但是,序列中间的分辨率值不在最后一个回调的范围内,我该如何访问它们?
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?
});
}
15 回答
我想你可以使用RSVP的哈希值 .
如下所示:
节点7.4现在支持带有和声标志的异步/等待调用 .
试试这个:
并运行该文件:
node --harmony-async-await getExample.js
简单就可以!
同步检查
将promises-for-later-needed-values分配给变量,然后通过同步检查获取它们的值 . 该示例使用bluebird的
.value()
方法,但许多库提供类似的方法 .这可以用于任意数量的值:
简单方法:D
这几天,我也遇到了一些像你这样的问题 . 最后,我找到了一个很好的解决方案,这个问题简单易读 . 我希望这可以帮到你 .
根据how-to-chain-javascript-promises
好吧,让我们来看看代码:
嵌套(和)闭包
使用闭包来维护变量的范围(在我们的例子中,成功回调函数参数)是自然的JavaScript解决方案 . 有了promises,我们可以任意回调 - 它们在语义上是等价的,除了内部的范围 .
当然,这是 Build 一个缩进金字塔 . 如果缩进变得太大,您仍然可以应用旧工具来对抗pyramid of doom:modularize,使用额外的命名函数,并在不再需要变量时立即展平promise链 .
理论上,你总是可以避免两个以上的嵌套级别(通过使所有闭包显式化),在实践中使用尽可能多的合理 .
您也可以使用这种partial application的辅助函数,如Underscore / lodash或native .bind() method的
_.partial
,以进一步减少缩进:可变的上下文状态
琐碎(但不优雅且相当错误)的解决方案是只使用更高范围的变量(链中的所有回调都可以访问)并在获取它们时将结果值写入它们:
可以使用(最初为空的)对象而不是许多变量,在该对象上将结果存储为动态创建的属性 .
该解决方案有几个缺点:
Mutable state is ugly和global variables are evil .
这种模式不能跨函数边界工作,模块化函数更难,因为它们的声明不能离开共享范围
变量的范围不会阻止在初始化之前访问它们 . 这尤其适用于可能发生竞争条件的复杂承诺构造(循环,分支,排除) . 明确地传递状态,承诺鼓励,强制更清晰的编码风格,可以防止这种情况 .
必须正确选择这些共享变量的范围 . 它必须是执行函数的本地函数,以防止多个并行调用之间的竞争条件,例如,如果状态存储在实例上就是这种情况 .
Bluebird库鼓励使用传递的对象,使用their bind() method将上下文对象分配给promise链 . 它可以通过其他不可用的this keyword从每个回调函数访问 . 虽然对象属性比变量更容易发现未检测到的拼写错误,但模式非常巧妙:
这种方法可以很容易地在不支持.bind的promise库中进行模拟(尽管以更冗长的方式并且不能在表达式中使用):
明确的传递
与嵌套回调类似,此技术依赖于闭包 . 然而,链条保持平稳 - 而不是只传递最新结果,每一步都会传递一些状态对象 . 这些状态对象会累积先前操作的结果,再次传递稍后将需要的所有值以及当前任务的结果 .
这里,小箭头
b => [resultA, b]
是关闭resultA
的函数,并将两个结果的数组传递给下一步 . 它使用参数解构语法再次在单个变量中进行分解 .在使用ES6进行解构之前,许多promise库(Q,Bluebird,when,...)提供了一个名为
.spread()
的漂亮助手方法 . 它需要一个具有多个参数的函数 - 每个数组元素一个 - 用作.spread(function(resultA, resultB) { …
.当然,这里所需的闭包可以通过一些辅助函数进一步简化,例如,
或者,您可以使用
Promise.all
来生成数组的承诺:你可能会不仅使用数组,而且使用任意复杂的对象 . 例如,在不同的辅助函数中使用_.extend或Object.assign:
虽然这种模式保证了扁平链条,而显式状态对象可以提高清晰度,但对于长链来说,这将变得乏味 . 特别是当您偶尔需要状态时,您仍然必须通过每一步 . 通过这个固定的接口,链中的单个回调相互紧密耦合,并且不灵活 . 它使得单个步骤的分解变得更加困难,并且不能直接从其他模块提供回调 - 它们总是需要包含在关心状态的样板代码中 . 像上面这样的抽象辅助函数可以缓解疼痛,但它总会存在 .
打破链条
当您需要访问链中的中间值时,您应该将链条拆分成您需要的那些单件 . 而不是附加一个回调并以某种方式尝试多次使用其参数,将多个回调附加到同一个承诺 - 无论您需要结果值 . 别忘了,promise just represents (proxies) a future value!接下来,在线性链中从另一个派生一个承诺,使用库提供给您的promise组合器来构建结果值 .
这将导致非常简单的控制流程,清晰的功能组合,因此易于模块化 .
在
Promise.all
之后只有ES6可用的回调中的参数解构,而在ES5中,then
调用将被许多promise库(Q,Bluebird,when,...)提供的漂亮帮助器方法取代:.spread(function(resultA, resultB) { …
.Bluebird还具有专用的join function,用更简单(更高效)的构造替换
Promise.all
spread
组合:另一个答案,使用顺序 Actuator nsynjs:
更新:添加了工作示例
另一个答案,使用
babel-node
版本<6使用
async - await
npm install -g babel@5.6.14
example.js:
然后,运行
babel-node example.js
瞧!我不会在我自己的代码中使用这种模式,因为我不是使用全局变量的忠实粉丝 . 但是,在紧要关头它会起作用 .
用户是一个有说服力的Mongoose模型 .
ECMAScript Harmony
当然,这个问题也得到了语言设计者的认可 . 他们做了很多工作,最终成功了
ECMAScript 8
您不再需要单个
then
调用或回调函数,如在异步函数中(在调用时返回promise),您只需等待promises直接解析即可 . 它还具有任意控制结构,如条件,循环和try-catch-clauses,但为了方便起见,我们在这里不需要它们:ECMAScript 6
在我们等待ES8的同时,我们已经使用了非常类似的语法 . ES6附带了generator functions,允许在任意放置的
yield
关键字中将执行分开 . 这些切片可以相互独立地运行,甚至是异步运行 - 而这正是我们在运行下一步之前等待promise解析时所做的事情 .有专门的库(如co或task.js),但是当你给它们一个产生promises的生成器函数时,许多promise库都有辅助函数(Q,Bluebird,when,...)为你做this async step-by-step execution .
从版本4.0开始,这在Node.js中工作,也有一些浏览器(或他们的开发版)相对较早地支持生成器语法 .
ECMAScript 5
但是,如果您希望/需要向后兼容,则无法使用没有转换器的那些 . 当前工具支持生成器函数和异步函数,例如参见generators和async functions上的Babel文档 .
然后,还有许多其他compile-to-JS languages致力于简化异步编程 . 它们通常使用类似于
await
的语法(例如Iced CoffeeScript),但也有一些具有类似Haskell的do
-notation(例如LatteJs,monadic,PureScript或LispyScript) .对“可变上下文状态”的不那么苛刻的旋转
使用本地范围的对象来收集承诺链中的中间结果是您提出的问题的合理方法 . 请考虑以下代码段:
全局变量很糟糕,因此该解决方案使用本地范围的变量,这不会造成任何伤害 . 它只能在功能中访问 .
可变状态是丑陋的,但这不会以丑陋的方式改变状态 . 丑陋的可变状态传统上是指修改函数参数或全局变量的状态,但是这种方法只是修改了一个本地范围的变量的状态,该变量的唯一目的是聚合承诺结果......一旦承诺结算,将会导致简单死亡的变量 .
中间承诺不会被阻止访问结果对象的状态,但这并没有引入一些可怕的场景,其中链中的一个承诺会变得流氓并破坏你的结果 . 在承诺的每个步骤中设置值的责任仅限于此函数,并且整体结果将是正确的或不正确的...它不会是多年后在 生产环境 中出现的错误(除非您打算将其用于!)
这不会引入并行调用引起的竞争条件场景,因为为getExample函数的每次调用都会创建一个新的结果变量实例 .
使用bluebird时,可以使用
.bind
方法在promise链中共享变量:请查看此链接以获取更多信息:
http://bluebirdjs.com/docs/api/promise.bind.html