我发现各种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的一些更复杂的用例。这显然是一个非常密集的话题,我发现的大多数材料都不能与我产生共鸣。
答案 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');
}
现在我们可以非常简单地转换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;
}
完成此操作后,我们只需要转换更复杂的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。在大多数情况下,您需要在链的末尾处理错误。