【问题标题】:Managing promise dependencies管理 Promise 依赖项
【发布时间】:2015-03-20 17:51:27
【问题描述】:

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

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

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

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 之前不需要依赖项 1,并且错误处理程序需要了解依赖项。

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

【问题讨论】:

  • 老实说,这对programmers.stackexchange.com 来说可能更好。
  • promise2 是否依赖于 promise1 完成?代码暗示了这一点,但有点不清楚。
  • 是的,以后出现的任何承诺都取决于之前的承诺所做的事情/它检索到的数据。

标签: javascript node.js promise bluebird


【解决方案1】:

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

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

但是,情况要糟糕得多,因为它不会让消费者处理他们可能能够处理的错误。

【讨论】:

  • 我觉得应该是finally而不是catch,否则成功情况下不做清理
  • @Esailija 他原来的例子有问题,但如果是资源清理,我同意最后是正确的方法。使用 disposer 模式可能更好,甚至更好的是实际的.disposer
  • 这正是我正在寻找的解决方案,我没有考虑注册多个 .then 回调(现在看起来很明显)。我有很多重构要做。谢谢。
  • ""//如果你真的需要在这里等待 p2,请执行。"请详细说明?
  • @Nepoxx 没什么好补充的——如果你也需要等待 p2,你可以Promise.join(p1, p2, promise3)——我只是假设 OP 等待了,但根据上面的问题代码不需要太多。跨度>
【解决方案2】:

这个问题可能更适合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);
};

【讨论】:

    【解决方案3】:

    thens 中返回 promise 肯定不是反模式,扁平化嵌套的 promise 是 promise 规范的一个特性。

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

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

    说真的,我觉得你无能为力来清理它。带有香草try/catches 的事件,最好的可能模式与这些非常相似。

    【讨论】:

    • 我觉得我好像遗漏了一些关于 Promise 的东西,因为大多数文档示例都是非常干净的链式形式。
    猜你喜欢
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 2017-12-24
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    相关资源
    最近更新 更多