javascript - 通过 Javascript 中的 promise 链传递状态有哪些模式?

标签 javascript node.js asynchronous promise chaining

我正在尝试学习一点关于 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); 
  });

有几件事我想过要做:

  1. 在 getPostsForUserId 函数中调用 getIdForUsername。 但是,我希望按照“做一件事,做好”的原则,让每个功能单元尽可能简单。
  2. 创建一个“上下文”对象并将其传递给整个链,读取并存储该对象中的状态。 但是这种方法使得每个函数都非常适合一个链,因此很难单独使用。

还有其他选择吗?推荐什么方法?

最佳答案

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

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

  1. .spread 接受一个返回数组并将其从数组参数更改为参数的 promise 。这允许将诸如 .then(result) { var postsA = result[0], postsB = result[1]; 之类的东西切割成 .spread(postsA,postsB.

  2. .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/

相关文章:

Javascript/Node.js 异步循环

javascript - 在 Javascript 中创建用于采样数据的时间戳对象数组?

javascript - 在 Promise 中包装异步循环

node.js - 为什么 Node 不能在 Windows 的 worker 中设置命名管道服务器?

javascript - 在 MongoDB 中搜索复数词形式时查找复数词和单数词

javascript - 我如何为我的 Electron 应用程序创建后端

javascript - 找不到动态生成的文本框

javascript - 如何使用页面名称作为文本框值

数组中的 JavaScript 图像

javascript - 我应该怎么做才能在我的网络应用程序中使用特定字体?