通过 Javascript 中的承诺链传递状态都有哪些模式? [复制]
Posted
技术标签:
【中文标题】通过 Javascript 中的承诺链传递状态都有哪些模式? [复制]【英文标题】:What patterns are there for passing state through a chain of promises in Javascript? [duplicate]通过 Javascript 中的承诺链传递状态有哪些模式? [复制] 【发布时间】:2014-05-18 12:44:41 【问题描述】:我正在尝试学习一点关于 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。 但是,我希望按照“做一件事,做好”的原则,让每个功能单元尽可能简单。
创建一个“上下文”对象并将其传递给整个链,在该对象中读取和存储状态。 但是这种方法使得每个函数都非常适合一个链,因此很难单独使用。
还有其他选择吗?推荐什么方法?
【问题讨论】:
FWIW,上下文对象选项并不是所有的那个都不好。如果您将getServiceAUserIdForUsername
函数视为“接受userName
参数并返回userId
”,那么“接受具有userName
属性的对象并填写userId
属性”不是那更具体到一个用例。那里肯定有更多的耦合(调用代码和函数需要就这些名称达成一致,通常他们不会),但事实是,如果你想要的不仅仅是“一个 arg 导致一个返回值”,你需要有某种方法来识别不同的位。
但是如果你愿意,我可以看到有一个通用的聚合器基础设施,所以只有调用代码知道这些属性名称。聚合器基础结构将接受您的“一个参数产生一个结果”启用 Promise 的函数的列表(因此这些函数不知道名称)和要使用的名称,并包装对它们的调用以填充对象。嗯。我想知道那会是什么样子。
感谢@t-j-crowder 关于通过管道传递的上下文对象,您是否知道任何专门使用此模式的 JS 库? Java有Apache Camel专门针对这种路由,我想知道js是否有类似的东西。
@Andy 我可以提出一个蓝鸟解决方案吗?它在这里减少了很多...
我会看看@benjamin-gruenbaum,谢谢。您认为还有其他值得评估的 Promise/A+ 实现吗?
【参考方案1】:
我会使用Promise.all
,就像这样
Promise.all([Promise.from('usernameA'), Promise.from('usernameB')])
.then(function(result)
return Promise.all([getUsername(result[0]),getUsername(result[1])])
)
.then(function(result)
return Promise.all([getPosts(result[0]),getPosts(result[1])]);
)
.then(function(result)
var postsA = result[0], postsB = result[1];
// Work with both the posts here
);
【讨论】:
我喜欢这个答案@thefourtheye。将数组中的 Promise 链按如下方式传递给 Promise.all 是否合理:gist.github.com/andystanton/f9ba6135a523971b6775 抱歉,50 次转发 - 我没有意识到您不能将代码放在对答案的回复中! @Andy 好吧,我得说,它看起来比我提出的解决方案更好:)【参考方案2】:首先是好问题。这是我们(至少我)经常处理承诺的事情。在我看来,这也是一个承诺真正超越回调的地方。
这里发生的事情基本上是你真的想要你的图书馆没有的两件事:
.spread
接受一个返回数组并将其从数组参数更改为参数的承诺。这允许将.then(result) var postsA = result[0], postsB = result[1];
之类的内容切割成.spread(postsA,postsB
。
.map
接受一组 promise 并将数组中的每个 promise 映射到另一个 promise - 类似于 .then
,但针对数组的每个值。
有两个选项,要么使用已经使用它们的实现,例如 Bluebird,我推荐它,因为它比现在的替代品要好得多(更快、更好的堆栈跟踪、更好的支持、更强大的功能集)或者你可以实施它们。
由于这是一个答案而不是图书馆推荐,让我们这样做:
让我们从传播开始,这相对容易——这意味着调用Function#apply
将数组传播到可变参数中。这是一个示例实现我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);
);
;
接下来,我们来做映射。 .map
on promises 基本上只是带有 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 会检测到它是一个地图链,并且会在第一个请求甚至没有完成的情况下将函数“推送”到第二个请求,所以 getUsername
对于第一个用户不会等到第二个用户,但实际上会调用getPosts
,如果这样更快,所以在这种情况下,它与您自己的 gist 版本一样快,同时更清晰 imo。
但是,它正在工作,而且很好。
Barebones A+ 实现更多是为了实现承诺库之间的互操作性,并且应该是“基线”。它们在设计特定平台的小型 API 时很有用——IMO 几乎从来没有。像 Bluebird 这样的可靠库可以显着减少您的代码。你正在使用的 Promise 库,甚至在他们的文档中说:
它旨在让基础知识正确,以便您可以在其之上构建扩展的 Promise 实现。
【讨论】:
以上是关于通过 Javascript 中的承诺链传递状态都有哪些模式? [复制]的主要内容,如果未能解决你的问题,请参考以下文章