在Javascript中通过一系列承诺传递状态的模式是什么?

我正在尝试学习一些关于Node和asynchronous编程的知识。 我阅读了有关Promises的信息,并试图在一个小型的项目中使用它们,这个小型的项目将服务A的用户从服务A复制到服务B.我很难理解如何在Promise之间传递状态

该项目是使用Promise库为NodeJS编写的

我目前的问题的一个简单的定义是:

  • 如果post在服务B中不存在,则将用户的post从服务A复制到服务B.
  • 这两种服务都提供了http API,它需要一个不可重复的用户标识来查找该用户的post,所以用户标识必须从用户名中查找。
  • 所有的http调用都是asynchronous的。

这是一些伪代码,说明我如何将Promises链接在一起。

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

有一些我曾经想过的事情:

  1. 在getPostsForUserId函数中引入getIdForUsername调用。 但是 ,我想按照“做一件事,做得好”的原则,尽可能简化每一个function单元。
  2. 创build一个“上下文”对象,并将其传递到整个链中,读取和存储此对象中的状态。 然而,这种方法使得每个function非常适合于一个链,因此很难单独使用。

有没有其他的select,build议使用什么方法?

我会使用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 }); 

首先是好问题。 这是我们(至less我)经常处理的承诺。 在我看来,这也是一个让承诺能真正照亮callback的地方。

这里发生的事情基本上是你真的想要两件你的图书馆没有的东西:

  1. .spread承诺返回一个数组并将其从数组参数更改为参数。 这允许切割.then(result) { var postsA = result[0], postsB = result[1];.spread(postsA,postsB

  2. .map ,它接受一个promise数组,并将数组中的每个promise都映射到另一个promise上 – 就像.then但是对于数组的每个值。

有两个select,要么使用像Bluebird这样的已经使用它们的实现,因为它比目前的select要好得多(更快,更好的堆栈跟踪,更好的支持,更强大的function集),或者你可以实现它们。

既然这是一个答案,而不是一个图书馆的build议,让我们这样做:

让我们从传播开始,这是相对容易的 – 所有这一切意味着调用Function#apply将数组扩展到可变参数。 这里是我从自己偷来的示例实现:

 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基本上只是一个数组映射:

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

这是我们一直想要的语法。 没有代码重复,干干净净,简洁明了,承诺之美。

请注意,这并不会玷污Bluebird所做的事情 – 例如,Bluebird将会检测到它是一个地图链,并且会在第二个请求没有完成第一个请求的情况下将“推送”到第二个请求,所以第一个用户的getUsername获得不要等待第二个用户,但如果更快,实际上会调用getPosts ,所以在这种情况下,它和自己的要点版本一样快,而且更清晰。

然而,它正在工作,而且很好。

Barebones A +实现更多的是承诺库之间的互操作性,应该是一个“基准线”。 在devise特定的平台小型API时它们非常有用 – IMO几乎从来没有。 像Bluebird这样一个稳定的库可以大大减less你的代码。 你正在使用的Promise库,甚至在他们的文档中说:

它旨在正确地获得基础知识,以便您可以在其上构build扩展的承诺实现。