更复杂的延迟/承诺使用和链只是't单击.希望能澄清一下

More complex Deferred/promise usage and chains just aren't clicking. Would love some clarification

本文关键字:希望 单击 一下 承诺 延迟 复杂      更新时间:2023-09-26

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

然而,当涉及到更复杂的用例(5+函数/方法链;嵌套的Deferred;从其他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/promise用例应该如何处理。这显然是一个非常密集的话题,我发现的大多数材料都没有引起我的共鸣

始终从同步代码开始。它更容易理解,而且转换为异步代码从来没有那么困难。如果你包含了如何编写同步代码,而不仅仅是一些稍微损坏的异步代码,我会更容易理解你想要代码做什么

简短回答

您最初的问题可能只是在无意的地方添加了括号。您应该始终将函数传递给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');
    })
}

如果您修复了括号问题(Forbes指出),那么您的第一个示例应该输出:

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。在大多数情况下,您只需要在链的末尾处理一次错误。

相关文章: