使用带有 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.log
在map
中, 打印列表 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 p1
、p2
、p3
解析为 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”返回有序和无序的结果的主要内容,如果未能解决你的问题,请参考以下文章
在带有辅助函数的 ejs 中使用 .then 返回 [ object promise ]
带有嵌套地图的 Promise.all .. 第一张地图仅适用于其他人在猫鼬中返回空对象