在Javascript中通过一系列承诺传递状态有哪些模式?

And*_*ton 9 javascript asynchronous chaining node.js promise

我正在尝试学习一些关于Node和异步编程的知识.我读到了Promises,并试图在一个小项目中使用它们,这个项目将用户的帖子从服务A复制到服务B.我在理解如何最好地在Promises之间传递状态时遇到一些麻烦

该项目是使用Promise库为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); 
  });
Run Code Online (Sandbox Code Playgroud)

我想过要做的一些事情:

  1. 将getIdForUsername调用带入getPostsForUserId函数.但是,我希望保持每个功能单元尽可能简单,遵循"做一件事,做得好"的原则.
  2. 创建一个"上下文"对象并将其传递给整个链,在此对象中读取和存储状态.然而,这种方法使得每个功能都非常适用于链条,因此难以单独使用.

还有其他选择,建议采用什么方法?

Ben*_*aum 6

首先是好问题.这是我们(至少我)经常处理承诺的事情.在我看来,它也是一个承诺真正超越回调的地方.

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

  1. .spread它接受一个返回数组并将其从数组参数更改为参数的promise.这使得切割之类的东西.then(result) { var postsA = result[0], postsB = result[1];进入.spread(postsA,postsB.

  2. .map它接受一个promises数组并将数组中的每个promise映射到另一个promise - 就像.then数组的每个值一样.

有两个选项,要么使用已经使用它们的实现,就像我推荐的Bluebird一样,因为它比现在的替代品要好得多(更快,更好的堆栈跟踪,更好的支持,更强大的功能集)或者你可以实现它们.

既然这是一个答案而不是图书馆推荐,那就让我们这样做:

让我们从传播开始,这是相对容易的 - 所有这意味着调用Function#apply将数组传播到varargs.以下是我自己偷走的示例实现:

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

    };
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们来做映射..map在promises上基本上只是一个带有a的数组映射:

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
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

为方便起见,我们可以引入一个静态对应物.map来启动链:

Promise.map = function(arr,mapping){
     return Promise.resolve(arr).map(mapping);
};
Run Code Online (Sandbox Code Playgroud)

现在,我们可以像我们想要的那样编​​写代码:

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
});
Run Code Online (Sandbox Code Playgroud)

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

请注意,这并不会划伤Bluebird所做的事情 - 例如,Bluebird将检测到它是一个地图链并将"推送"功能打开到第二个请求,而第一个请求甚至完成,因此getUsername第一个用户赢了"等待第二个用户,但实际上会调用,getPosts如果这更快,所以在这种情况下,它与你自己的要点版本一样快,同时更清晰的imo.

但是,它工作正常,很好.

Barebones A +实现更多用于promise库之间的互操作性,并且应该是一个"基线".它们在设计特定平台小型API时很有用 - IMO几乎从不.像Bluebird这样的可靠库可以显着减少您的代码.您正在使用的Promise库,甚至在他们的文档中说:

它旨在使基础知识正确,以便您可以在其上构建扩展的承诺实现.


the*_*eye 3

我会用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
    });
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

1353 次

最近记录:

11 年,8 月 前