你能“累积”承诺结果形成一连串“那么”吗? [复制]
Posted
技术标签:
【中文标题】你能“累积”承诺结果形成一连串“那么”吗? [复制]【英文标题】:Can you "accumulate" promise results in a chain of "then"? [duplicate] 【发布时间】:2014-10-15 09:59:19 【问题描述】:假设您必须链接返回承诺的函数,其中每个函数都需要 一些(不一定是最后一个)其他承诺返回的值。
这个模式有名字吗,用 promises 干净可行吗?
例如:
return getA().then(function (a)
getB(a).then(function (b)
getC(a, b).then (function (c)
return
a : a,
b : b,
c : c
);
);
);
你不能“天真”地把它弄平:
getA().then(function (a)
return getB(a);
).then(function (b)
// Obviously won't work since a is not there any more
return getC(a, b);
).then(function (c)
// Same game, you can't access a or b
);
我能看到的唯一替代方法是 getA、getB 和 getC 实际上返回一个 Promise,该 Promise 由一个包含 a、b、c 的对象解析。 (所以每个函数都会构建它是最终结果的一部分。)
getA().then (function (result)
return getB(result.a);
).then(function (result)
return getC(result.a, result.b);
).then(function (result)
// Last function is useless, just here to illustrate.
return result;
);
在我的第一个示例中,有没有办法将代码展平?
【问题讨论】:
You can't "naively" flatten this :
是的,你可以。 :) 使用 .then 修改 getB 和 getC 的结果
@KevinB 不确定我明白你的想法,你能补充一个例子吗?
嗯,我一直在尝试客观地看待这一点,但所有改进代码的建议主要都是基于意见的。但是,我可以说,您的第一个示例不是承诺链的正确方法。
【参考方案1】:
使用 Promise 的原因是为了避免嵌套回调。您的第一个示例导致承诺链回归到一组相互嵌套的回调函数。 (不要那样做)
链可以这样使用:
var first, second, third;
getA().then(function (a)
first = a;
return getB(first);
).then(function (b)
second = b;
return getC(first, second);
).then(function (c)
third = c;
return [first, second, third]; // have everything now!
);
您或许也应该再看看您的代码。一个理想的 Promise 链应该是这样的:
getA().then(getB).then(getC).then(function(result)
// done!
);
(当然,除非这些函数来自库。)
【讨论】:
我想 Promise 专家会不喜欢这个,因为它没有使用漂亮的 Promise 功能来解决问题,但我喜欢它,因为坦率地说,它是所有这些中最清晰和最明显的解决方案。即使是没有承诺的专家也可以看一眼代码,看看它在做什么。 是的,我应该澄清这一点,但情况通常是 getA / getB / getC 来自库或足够通用以至于您无法调整它们的返回值。【参考方案2】:如果你的 Promise 库支持 .all()
(或者你正在使用支持 .all()
的 ES6 标准 Promise),你应该可以这样做:
getA().then(function (a)
return Promise.all([a, getB(a)]);
).then(function (ab)
return Promise.all(ab.concat(getC(ab[0], ab[1])));
).then(function (abc)
return a : abc[0], b : abc[1], c : abc[2] ;
);
另一方面,tcooc 关于提前声明变量来保存结果的建议非常简单,绝对值得考虑。
【讨论】:
我不怀疑这可能会奏效,但是对于一个相当简单的问题来说,这很混乱。可读性/操作意图的基准似乎是 tcooc 的蛮力,但显而易见的解决方案,这个解决方案似乎比 tcooc 的答案更易读、更难理解和更难维护。【参考方案3】:这里有一些你实际上并不需要的东西:
额外的闭包变量 任何嵌套。或者嵌套 Promise.all 调用 实现复杂的逻辑这里的技巧是使用 Promise 作为 值的代理,这可以让您像使用其他顺序代码一样逐行执行此操作。这是“本机展平”:
var a = getA();
var b = a.then(getB);
var c = Promise.all([a,b]).spread(getC); // .all with a lambda if no .spread
现在假设您要访问a
b
或c
:
// .then and unwrap arguments if no spread, in Bluebird it's even better with join
Promise.all([a,b,c]).spread(function(a, b, c)
//a b and c are available here, note how we didn't need to nest
);
【讨论】:
`.spread() 是 Bluebird 特有的功能吗?这可以通过 Chrome/node 中内置的标准 ES6 Promise 来完成吗? @jfriend00 是的,.spread
只是 ES6 解构的一种快捷方式,但它还没有在浏览器中实现,所以 Promise 库实现了.spread
。毕竟.spread
所做的就是.then(r) return fn.apply(null, r) )
,所以它是一个简单的垫片。
这太可怕了。最终的消费者功能还不错,但看看创建a
、b
尤其是c
所涉及的尴尬!
@Roamer-1888 我认为考虑到 OP 的需求,这是迄今为止最简单、最优雅的方法,而且物有所值 - 有了一个可靠的 promise 库,这要容易得多。 c
将是 Promise.join(a, b, getC)
。你当然可以使用普通的.then
。如果您想更优雅地解开每个阶段的值 - 您可以使用生成器,这正是当yield
仅使用更好的语法解析承诺时所做的。
嗨本杰明。对不起,“可怕”是不友善的。更好的说法是,你可能在这类事情上拥有 99% 的能力。你沉浸在其中,有 Bluebird 的开发等等。我毫不怀疑您的解决方案是完全正确的并且肯定简洁,但是大多数程序员将无法为自己编写 Promise.all([a,b]).spread(getC)
甚至 Promise.join(a, b, getC)
。作为公认的答案,这似乎可以满足 OP,但这是一个很好的通用建议吗?值得深思。【参考方案4】:
是的,使用“累加器模式”。
编写 getA
、getB
和 getC
来接受一个对象,扩充它,然后返回它或用它解决的新承诺。
例如:
function getA(obj)
// ...
// read/augment obj
// ...
return obj; // or return Promise.resolve(obj);
现在,调用表达式可以是一个超扁平链:
function foo()
var obj =
//initial properties
;
return Promise.resolve(obj).then(getA).then(getB).then(getC);
因此obj
是一个在链上传递的令牌。任何处理器函数 getA
/getB
/getC
对 obj
所做的任何事情都可以被随后的所有处理器函数观察到。
显然,所有处理器功能(可能除了链中的最后一个功能)都必须受到约束:
它们必须遵守协议(接受/增加/返回对象) 他们不得不恰当地覆盖或以其他方式破坏已存在的 obj 属性。因此,“累加器模式”不能直接与任何旧的第三方函数一起使用,尽管此类函数的自适应包装器应该很容易编写。
DEMO
【讨论】:
以上是关于你能“累积”承诺结果形成一连串“那么”吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C++ 中使用 for_each 累积结果? [复制]