更复杂的延迟/承诺使用和链只是没有点击。希望得到一些澄清

时间:2013-07-08 00:06:11

标签: javascript node.js asynchronous jquery-deferred promise

我发现各种Deferred / promise库的文档通常包含非常简单的用例,例如链接1或2个函数/方法,从而导致解决或拒绝成功或错误。

然而,当谈到更复杂的用例(5个以上函数/方法的链;嵌套的Deferreds;从其他Deferred中返回promise)时,我会空手而归,变得非常沮丧。

比如说,我有一个包含2个嵌套函数的函数。每个子函数都返回一个promise,我希望父函数返回子函数的成功/失败结果:

var saveUser = function(user) {
    var saveNewUser = function(user) {
        var deferred = when.defer();

        user.save(function (err) {
            if (err) {
                deferred.reject();
            }
            else {
                // forcing a rejection here for testing purposes
                deferred.reject(6);
            }
        });

        return deferred.promise;
    }

    var supplyUserCollections = function() {
        var deferred = when.defer();

        deferred.reject();

        return deferred.promise;
    }

    return saveNewUser(user).then(supplyUserCollections(), function() {
        console.log('failed to save new user');
    }).then(function(data) {
        console.log('succeeded to do everything');
    }, function() {
        console.log('failed to seed new user collections');
    });
}

这不起作用;奇怪的是,“成功地做了所有事情”console.log被激怒,即使我强迫拒绝/失败两个孩子的功能。看作.then的第一个参数就是成功案例,我真的不明白为什么会这样。此外,假设父函数saveUser是另一个广泛的承诺链的一部分,例如:

dropExistingCollections(collections).then(saveEntities(albums), function() {
    console.log('failed to drop existing collections');
}).then(saveEntities(movies), function() {
    console.log('failed to save all albums');
}).then(saveEntities(games), function() {
    console.log('failed to save all movies');
}).then(saveUsers(users), function() {
    console.log('failed to save all games');    
}).then(function(data) {
    console.log(data);

    console.log('successfully saved all seed data');

    res.send('database data wiped and re-seeded');
}, function() {
    console.log('failed to save all users');
});

我不确定如何以一种可与其余函数链接的方式正确地从saveUser返回一个promise,这些函数都是返回已解析/拒绝Deferred的简单函数。

我真的只是想更确切地说明如何处理Deferreds / promises的一些更复杂的用例。这显然是一个非常密集的话题,我发现的大多数材料都不能与我产生共鸣。

2 个答案:

答案 0 :(得分:5)

始终从同步代码开始。它更容易理解,并且异步代码的转换从未如此困难。如果您已经包含了编写同步代码的方式,而不仅仅是一些稍微破坏的异步代码,那么我可以更容易地找出您希望代码执行的操作。

简短回答

您最初的问题可能只是您在不想要的地方添加了括号。您应始终将函数传递给then处理程序:

return saveNewUser(user).then(supplyUserCollections /* NOTE: no parenthesis */, function() {
    console.log('failed to save new user');
}).then(function(data) {
    console.log('succeeded to do everything');
}, function() {
    console.log('failed to seed new user collections');
});

这似乎是你对第二个功能的主要误解。

答案很长

想象一下您想要编写的代码可能类似于以下内容(如果用户有saveSync方法和save方法)。

function saveNewUser(user) {
  var result = user.saveSync();//may throw
  // forcing a throw here for testing purposes
  throw 6;
}

function supplyUserCollections() {
  throw new Error('failed supplyUserCollections');
}

function saveUser(user) {
  try {
    saveNewUser(user);
  } catch (ex) {
    console.log('failed to save new user');
    return;
  }
  try {
    supplyUserCollections();
  } catch (ex) {
    console.log('failed to seed new user collections');
    return;
  }
  console.log('succeeded to do everything');
}

现在我们可以非常简单地转换saveNewUsersupplyUserCollections

function saveNewUser(user) {
  var deferred = when.defer();

  user.save(function (err) {
    if (err) {
      deferred.reject();
    }
    else {
      // forcing a rejection here for testing purposes
      deferred.reject(6);
    }
  });

  return deferred.promise;
}

function supplyUserCollections() {
  var deferred = when.defer();

  deferred.reject();

  return deferred.promise;
}

完成此操作后,我们只需要转换更复杂的saveUser方法:

function saveUser(user) {
  saveNewUser(user)
    .then(function () {
      //this only happens if `saveNewUser` succeeded, like the bit after
      //a catch block containing a `return`
      return supplyUserCollections()
        .then(function () {
          console.log('succeeded to do everything');
        }, function (ex) {
          console.log('failed to seed new user collections');
        })
    }, function (ex) {
      //this happens when `saveNewUser` failed (just like the catch block)
      console.log('failed to save new user');
    })
}

现在在我看来,更可能的同步函数版本可能是这样的:

function saveUser(user) {
  saveNewUser(user);
  supplyUserCollections();
  console.log('succeeded to do everything');
}

你可以写成:

function saveUser(user) {
  saveNewUser(user)
    .then(function () {
      //this only happens if `saveNewUser` succeeded
      return supplyUserCollections()
    })
    .then(function () {
      //this happens when both methods succeeded
      console.log('succeeded to do everything');
    })
}

答案 1 :(得分:1)

如果你修正了括号问题(福布斯指出),那么你的第一个例子应该输出:

failed to save new user
succeeded to do everything

所以现在你可能会质疑,为什么它在初始步骤失败时显示成功完成所有操作。这是因为在你的流程中,你超越了初始错误,并重新回到成功轨道。要保持错误状态,您需要在回调中重新抛出相同的错误:

saveNewUser(user).then(supplyUserCollections(), function(err) {
    console.log('failed to save new user');
    throw err; // Re-throw to maintain error state
})

有了这个,你应该看到:

failed to save new user
failed to seed new user collections

您的流程仍然与最佳做法不同。第一件事:您应该清楚地知道您的流程在哪一步因错误消息的内容而崩溃,并且您不应该在每一步都听错,它已经过时且过于冗长。

假设saveNewUser可能因无法保存用户错误而崩溃,supplyUserCollections可能会因无法播种新用户集而崩溃。 你的流程应该简单:

saveNewUser(user).then(supplyUserCollections).done(function () {
    console.log("Success!");
}, function (err) {
    console.error(err.message);
});

然后,如果任何步骤崩溃,您将看到相应的消息,这就是您应该如何有效地使用promises。在大多数情况下,您需要在链的末尾处理错误。