0

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

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

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

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会触发,即使我强制拒绝/失败两个子函数。看到 a 上的第一个参数.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与其他函数链接的方式正确地返回一个承诺,这些函数都是返回已解决/拒绝的延迟的简单函数。

我真的很想更清楚地说明应该如何处理这些更复杂的延迟/承诺用例。这显然是一个非常密集的话题,而且我发现的大部分材料都不是特别能引起我的共鸣。

4

2 回答 2

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

现在我们可以非常简单地saveNewUser转换:supplyUserCollections

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

完成此操作后,我们只需要 conver 更复杂的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');
    })
}
于 2013-07-08T11:11:26.147 回答
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);
});

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

于 2013-07-09T12:45:16.437 回答