通过 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 中的承诺链传递状态都有哪些模式? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

链式承诺不会在拒绝时传递

javascript 带有取消的多个承诺链

javascript 取消承诺链

如何处理错误然后立即脱离承诺链?

Javascript - 链多个获取承诺

javascript设计模式——职责链模式