管理承诺依赖项

时间:2015-01-22 02:17:00

标签: javascript node.js promise bluebird

我使用Node.js和Bluebird来创建一些相当复杂的逻辑,包括解压缩结构化文件,解析JSON,创建和更改几个MongoDB文档,以及在多个位置编写相关文件。我还对所有这些进行了相当复杂的错误处理,具体取决于发生错误时系统的状态。

我很难想出通过承诺流来管理依赖关系的好方法。

我现有的代码基本上是这样的:

var doStuff = function () {
  var dependency1 = null;
  var dependency2 = null;

  promise1()
  .then(function (value) {
    dependency1 = value;

    return promise2()
    .then(function (value) {
      dependency2 = value;

      return promise3(dependency1)
      .then(successFunction);
    });
  })
  .catch(function (err) {
    cleanupDependingOnSystemState(err, dependency1, dependency2);
  });
};

请注意,promise3在promise3之前不需要,并且错误处理程序需要知道依赖项。

对我而言,这似乎是意大利面条代码(而且我的实际代码在很多并行控制流程中更糟糕)。我还读到,在.then回调中返回另一个承诺是反模式。是否有更好/更清洁的方式来完成我想要做的事情?

3 个答案:

答案 0 :(得分:9)

我发现这两个答案目前提供的很好但很笨拙。它们都很好但是包含了我认为你不需要的开销。如果你改为使用promises作为代理,你可以免费获得很多东西。

var doStuff = function () {
  var p1 = promise1();
  var p2 = p1.then(promise2);
  var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do.
  return Promise.all([p1, p2, p3]).catch(function(err){
      // clean up based on err and state, can unwrap promises here
  });
};

请不要使用successFunction这样的反模式并丢失信息。  如果您觉得必须使用successFunction,可以写下:

var doStuff = function () {
  var p1 = promise1();
  var p2 = p1.then(promise2);
  var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do.
  Promise.join(p1, p2, p3, successFunction).catch(function(err){
      // clean up based on err and state, can unwrap promises here
  });
};

然而,它无限恶化,因为它不会让消费者处理他们可能处理的错误。

答案 1 :(得分:1)

这个问题可能更适合code review,但在这个例子中,我的方法就是这样:

var doStuff = function () {
  // Set up your promises based on their dependencies. In your example
  // promise2 does not use dependency1 so I left them unrelated.
  var dep1Promise = promise1();
  var dep2Promise = promise2();
  var dep3Promise = dependency1Promise.then(function(value){
    return promise3(value);
  });

  // Wait for all the promises the either succeed or error.
  allResolved([dep1Promise, dep2Promise, dep3Promise])
      .spread(function(dep1, dep2, dep3){

    var err = dep1.error || dep2.error || dep3.error;
    if (err){
      // If any errored, call the function you prescribed
      cleanupDependingOnSystemState(err, dep1.value, dep2.value);
    } else {
      // Call the success handler.
      successFunction(dep3.value);
    }
};

// Promise.all by default just fails on the first error, but since
// you want to pass any partial results to cleanupDependingOnSystemState,
// I added this helper.
function allResolved(promises){
  return Promise.all(promises.map(function(promise){
    return promise.then(function(value){
      return {value: value};
    }, function(err){
      return {error: err};
    });
  });
}

使用allResolved只是因为你的回调细节,如果你有一个更通用的错误处理程序,你可以直接使用Promise.all解决,甚至:

var doStuff = function () {
  // Set up your promises based on their dependencies. In your example
  // promise2 does not use dependency1 so I left them unrelated.
  var dep1Promise = promise1();
  var dep2Promise = promise2();
  var dep3Promise = dependency1Promise.then(function(value){
    return promise3(value);
  });

  dep3Promise.then(successFunction, cleanupDependingOnSystemState);
};

答案 2 :(得分:1)

then内返回承诺肯定不是反模式,扁平化嵌套承诺是承诺规范的一个特征。

这是一个可能的重写,虽然我不确定它更干净:

var doStuff = function () {

  promise1()
  .then(function (value1) {

    return promise2()
    .then(function (value2) {

      return promise3(value1)
      .then(successFunction)
      .finally(function() {
        cleanup(null, value1, value2);
      });

    })
    .finally(function() {
      cleanup(null, value1, null);
    });

  })
  .finally(function () {
    cleanup(null, null, null);
  });

};

或另一种选择,具有原子清理功能:

var doStuff = function () {

  promise1()
  .then(function (value1) {

    return promise2()
    .then(function (value2) {

      return promise3(value1)
      .then(successFunction)
      .finally(function() {
        cleanup3(value2);
      });

    })
    .finally(function() {
      cleanup2(value1);
    });

  })
  .finally(function (err) {
    cleanup1(err);
  });

};

真的,我觉得你无法做很多事情来清理它。使用vanilla try/catch es的事件,最好的模式与这些模式非常相似。