使用带有 promise 的“map”返回有序和无序的结果

Posted

技术标签:

【中文标题】使用带有 promise 的“map”返回有序和无序的结果【英文标题】:Using "map" with promises returns ordered and unordered results 【发布时间】:2019-05-03 02:59:55 【问题描述】:

我有这段代码:

const Axios = require('axios');
const baseURL = 'https://jsonplaceholder.typicode.com';

async function main() 
    const posts = await Axios(`$baseURL/posts`);
    const users = await Promise.all(posts.data.map( async (post, index) => 
        let d = await Axios(`$baseURL/users/$post.userId`);
        return d.data.name;
    ))

    users.map( (user, index) => 
        console.log( `$index. $user` );
    );

并按顺序输出结果:

1. Patricia
2. Glenna
3. Nicholas
4. Kurtis
5. Chelsey

一切都好,但是......如果我这样做:

async function main() 
    const posts = await Axios(`$baseURL/posts`);
    await Promise.all(posts.data.map( async (post, index) => 
        let d = await Axios(`$baseURL/users/$post.userId`);
        console.log( `$index. $d.data.name` );
    ))

map 内的console.log 打印无序列表...

2. Glenna
4. Kurtis
5. Chelsey
1. Patricia
3. Nicholas

我的问题是:

为什么在第一个代码map返回列表有序,而在第二个console.logmap中, 打印列表 unordered ?

【问题讨论】:

.map() 不注意异步回调正在返回的承诺,因此它不会等待它解决。因此,您所做的只是一次启动一堆异步操作,然后查看哪些先完成。 @jfriend00 那么,await 在等待 promise (Axios) 结果时不会停止 .map() 迭代过程吗? 是的,没错。您的 .map() 不会等待回调中的 await。因此,第二个 .map() 中的 console.log() 语句只是您发起的所有请求之间的竞赛,它们可以按任何顺序完成。 Promise.all() 将它们全部收集起来并为您按正确的顺序排列,因此当您查看 await Promise.all() 的结果时,您会按顺序恢复它们。 【参考方案1】:

因为Promise.all 并行执行promise 并且是异步的,而.map 阻塞并按顺序执行,直到遍历所有项目才会完成。这就像 for-each 循环。

如果你想实现排序,我建议你使用Bluebird.each(库)或类似的东西:

const promiseEach = promises => 
  const results = [];

  return promises
    .reduce((acc, val, idx) => acc.then(_ => ((idx > 0 && results.push(_)), val)), Promise.resolve())
    .then(_ => [...results, _]);


const a1 = Promise.resolve(1);
const a2 = Promise.resolve(2);
const a3 = Promise.resolve(3);

const d1 = new Promise((resolve, reject) =>  setTimeout(() => resolve(1), 3000); ); // resolves after 3 seconds.
const d2 = new Promise((resolve, reject) =>  setTimeout(() => resolve(2), 2000); ); // resolves after 2 seconds.
const d3 = new Promise((resolve, reject) =>  setTimeout(() => resolve(3), 1000); ); // resolves after 1 seconds.

// this will respect orderings, before first promise is not resolved, does not goes to next one.
promiseEach([a1, a2, a3, d1, d2, d3]).then(console.log);

并将您的地图传递给promiseEach 函数,它们将被订购。

【讨论】:

您能否详细说明您的reduce?我迷路了 哦.. 嗯。它所做的是,做出相互链接的承诺。例如,如果你有 N 个 promise (a1, a2, ... aN),这个 reduce 将产生类似:Promise.resolve().then(() => a1).then(() => a2).then(...).then(() => aN)(但也返回从每个链返回的结果)这意味着,除非第一个 promise 没有解决,否则它会赢'不继续下一条链,依此类推。 EDIT2:建议你看看bluebird的each方法(bluebirdjs.com/docs/api/promise.each.html) 好的。我来看看Bluebird's each。关于reduce,我是这样写的:const promiseEach = async (promises) => promises.reduce(async (acc, val) => const collection = await acc; collection.push(await val); return collection , (async () => [])()) javascript 太棒了! Ehm .. 我不建议 ;d 首先,你有额外的 async 你并不真正需要(async (promises) => ...),其次你只能传递数组(不是 @ 987654333@),所以最后会变成这样:const promiseEach = promises => promises.reduce(async (acc, val) => acc.push(await val); return acc; , []);【参考方案2】:

Promise.all 旨在保持传递给它的承诺结果的顺序,与这些承诺实际解决的顺序无关。因此,当Promise.all 解析时,这意味着所有单独的 Promise 都已解析,然后 Promise.all 解析为解析数组以与数组中对应的 Promise 相同的顺序

但是,如果您在它们的承诺解决后立即输出值,那么上述内容当然对您的输出没有影响——现在它将按照各个承诺解决的顺序进行排序.

简单示例:

假设有三个 Promise p1p2p3 解析为 1、2 和 3。但第二个 Promise 将比其他两个更快地完成。

Promise.all[p1, p2, p3] 调用。它返回一个新的承诺,最终将解析为[1, 2, 3]。在不是所有的 Promise 都被解决的时候,你可以想象一个内部数组的演变如下:

p2 解决。 Promise.all内部存储[undefined, 2, undefined] p1 解决。 Promise.all内部存储[1, 2, undefined] p3 解决。 Promise.all 也解析为值 [1, 2, 3]

在这里您可以看到第一个代码将输出 2, 1, 3 而第二个代码将输出 1, 2, 3

【讨论】:

谁在我的第一个代码中保留订单:Promise.all.map() 另一个问:await Axios($baseURL/users/$post.userId)在等待promise (Axios)结果的时候停止了.map()的迭代过程? map 也保持顺序但是同步的。在您的第一个代码中,它返回保持秩序的承诺。 Promise.all 只返回一个 promise,但它会异步解析为反映原始顺序的数组。 内部await Axios 不会停止map 进程。 await 使对 map 的回调立即返回一个新的承诺。 map 立即返回所有创建的 Promise。 确实,await 没有阻塞。 async 函数中的第一个实际上使该函数返回一个承诺,并且在该函数调用之后继续执行代码。只有当 JS 调用堆栈为空并且 await-ed 承诺解析时,函数的上下文才会恢复,然后在 await 之后继续执行。另见解释here 和here。【参考方案3】:

当您使用 Promise.all(requests) 时,所有请求都是并行发出的,因此您无法知道哪个在另一个之前结束。

在第一个代码中,您已经按照请求数组的顺序获得了结果。但在第二个中,console.log 是按响应顺序排列的。

【讨论】:

【参考方案4】:

因为如果您使用异步代码,您“触发”请求的顺序无关紧要,它只计算响应所需的时间。

所以你的结果将按照你的请求完成的方式排序,所以如果你对 x 的请求首先完成,即使你最后触发它,它仍然会在你的结果的第一个位置

map 函数是“阻塞”的,这意味着第二个请求在第一个请求完成后触发,依此类推。

这里是一个例子:https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/

【讨论】:

是的,但是为什么第一个代码的结果是按顺序返回的呢? map 函数是“阻塞”的,这意味着在第一个请求完成后触发第二个请求,依此类推。

以上是关于使用带有 promise 的“map”返回有序和无序的结果的主要内容,如果未能解决你的问题,请参考以下文章

在map函数中使用axios时返回一个promise数组?

每个循环中的有序 JavaScript Promise

在带有辅助函数的 ejs 中使用 .then 返回 [ object promise ]

带有嵌套地图的 Promise.all .. 第一张地图仅适用于其他人在猫鼬中返回空对象

在循环中使用带有 fs.readFile 的 Promises

在 AngularJS 中使用带有 Promises 的 success/error/finally/catch