你能“累积”承诺结果形成一连串“那么”吗? [复制]

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 bc

// .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) ),所以它是一个简单的垫片。 这太可怕了。最终的消费者功能还不错,但看看创建ab 尤其是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】:

是的,使用“累加器模式”。

编写 getAgetBgetC 来接受一个对象,扩充它,然后返回它或用它解决的新承诺。

例如:

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/getCobj 所做的任何事情都可以被随后的所有处理器函数观察到。

显然,所有处理器功能(可能除了链中的最后一个功能)都必须受到约束:

它们必须遵守协议(接受/增加/返回对象) 他们不得不恰当地覆盖或以其他方式破坏已存在的 obj 属性。

因此,“累加器模式”不能直接与任何旧的第三方函数一起使用,尽管此类函数的自适应包装器应该很容易编写。

DEMO

【讨论】:

以上是关于你能“累积”承诺结果形成一连串“那么”吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

你能在返回之前解决一个 angularjs 承诺吗?

女朋友:你能给我讲讲单例模式吗?

如何在 C++ 中使用 for_each 累积结果? [复制]

JS/jQuery 承诺

我可以使用原型模式而不是闭包模式来创建解析为对象的承诺吗? [复制]

then() 真的在所有情况下都返回一个承诺吗? [复制]