你能"积累"承诺导致一连串的"然后"?

时间:2014-08-21 17:01:01

标签: javascript promise

假设您必须链接返回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,它使用包含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;
});

有没有办法在我的第一个例子中展平代码?

4 个答案:

答案 0 :(得分:4)

使用promises的原因是为了避免嵌套回调。你的第一个例子使得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!
});

你也应该再看一下你的代码。一个理想的承诺链应该是这样的:

getA().then(getB).then(getC).then(function(result) {
  // done!
});

(当然,除非这些功能来自图书馆。)

答案 1 :(得分:4)

以下是您实际不需要的内容:

  • 其他关闭变量
  • 任何筑巢。或嵌套Promise.all调用
  • 实施复杂的逻辑

这里的诀窍是使用promises作为值的代理,它可以让你在行之后执行此行,就像使用其他顺序代码一样。这是“原生扁平化”:

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

答案 2 :(得分:2)

是的,使用"累加器模式"。

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是一个传递给链的令牌。任何处理器函数obj / getA / getBgetC所执行的任何操作都可由后续的所有处理器函数观察到。

显然,所有处理器功能(可能是链中的最后一个)必须遵守规则:

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

出于这个原因,"累加器模式"不能直接用于任何旧的第三方函数,尽管这些函数的自适应包装应该很容易编写。

<强> DEMO

答案 3 :(得分:0)

如果您的承诺库支持.all()(或者您正在使用支持.all()的ES6标准承诺),您应该可以执行以下操作:

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建议事先声明变量以保持结果非常简单,绝对值得考虑。