我正在尝试学习一点关于 Node 和异步编程的知识。我阅读了 Promises 并尝试在一个小型项目中使用它们,该项目将用户的帖子从服务 A 复制到服务 B。我在理解如何最好地在 Promises 之间传递状态时遇到了一些麻烦
该项目是使用 Promise library 为 NodeJS 编写的。
我当前问题的一个简单定义是:
- 如果服务 B 中不存在帖子,则将用户的帖子从服务 A 复制到服务 B。
- 这两个服务都提供了 http API,需要一个不可内存的用户 ID 来查找该用户的帖子,因此必须从用户名中查找用户 ID。
- 所有的 http 调用都是异步的。
这是一些伪代码,说明了我如何将 Promise 链接在一起。
Promise.from('service_A_username')
.then(getServiceAUserIdForUsername)
.then(getServiceAPostsForUserId)
.then(function(serviceAPosts) {
// but what? store globally for access later?
doSomethingWith(serviceAPosts);
return Promise.from('service_B_username');
})
.then(getServiceBUserIdForUsername)
.then(getServiceBPostsForUserId)
.done(function(serviceBPosts) {
// how do we interact with Service A posts?
doSomethingThatInvolvesServiceAPostsWith(serviceBPosts);
});
有几件事我想过要做:
- 在 getPostsForUserId 函数中调用 getIdForUsername。 但是,我希望按照“做一件事,做好”的原则,让每个功能单元尽可能简单。
- 创建一个“上下文”对象并将其传递给整个链,读取并存储该对象中的状态。 但是这种方法使得每个函数都非常适合一个链,因此很难单独使用。
还有其他选择吗?推荐什么方法?
最佳答案
首先是好问题。这是我们(至少我)经常处理 promise 的事情。在我看来,这也是一个 promise 真正超越回调的地方。
这里发生的事情基本上是你真的想要你的图书馆没有的两件事:
.spread
接受一个返回数组并将其从数组参数更改为参数的 promise 。这允许将诸如.then(result) { var postsA = result[0], postsB = result[1];
之类的东西切割成.spread(postsA,postsB
..map
接受一个 promise 数组并将数组中的每个 promise 映射到另一个 promise - 它类似于.then
但对于数组的每个值.
有两个选项,要么使用已经使用它们的实现,如 Bluebird我推荐它,因为它比现在的替代品要好得多(更快、更好的堆栈跟踪、更好的支持、更强大的功能集)或者你可以实现它们。
由于这是一个答案而不是图书馆推荐,让我们这样做:
让我们从传播开始,这相对容易——这意味着调用 Function#apply
将数组传播到可变参数中。这是一个示例实现 I stole from myself:
if (!Promise.prototype.spread) {
Promise.prototype.spread = function (fn) {
return this.then(function (args) {
//this is always undefined in A+ complaint, but just in case
return fn.apply(this, args);
});
};
}
接下来,我们来做映射。 promise 上的 .map
基本上只是带有 then 的数组映射:
if(!Promise.prototype.map){
Promise.prototype.map = function (mapper) {
return this.then(function(arr){
mapping = arr.map(mapper); // map each value
return Promise.all(mapping); // wait for all mappings to complete
});
}
}
为方便起见,我们可以引入 .map
的静态对应物来启动链:
Promise.map = function(arr,mapping){
return Promise.resolve(arr).map(mapping);
};
现在,我们可以按照我们真正想要的方式编写您的代码:
var names = ["usernameA","usernameB"]; // can scale to arbitrarily long.
Promise.map(names, getUsername).map(getPosts).spread(function(postsA,postsB){
// work with postsA,postsB and whatever
});
这是我们一直以来真正想要的语法。没有代码重复,DRY,简洁明了,promise 之美。
请注意,这并没有触及 Bluebird 所做工作的表面 - 例如,Bluebird 会检测到它是一个 map 链,并且会在第一个请求甚至没有完成的情况下将函数“推送”到第二个请求,所以 第一个用户的 getUsername
不会等到第二个用户,但实际上会调用 getPosts
如果这样更快,因此在这种情况下,它与您自己的 gist 版本一样快,同时更清晰 imo。
但是,它正在工作,而且很好。
Barebones A+ 实现更多是为了实现 promise 库之间的互操作性,并且应该是“基线”。它们在设计特定平台的小型 API 时很有用——IMO 几乎从来没有。像 Bluebird 这样的可靠库可以显着减少您的代码。你正在使用的 Promise 库,甚至在他们的文档中说:
It is designed to get the basics spot on correct, so that you can build extended promise implementations on top of it.
关于javascript - 通过 Javascript 中的 promise 链传递状态有哪些模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22892681/