显然,给定一个返回承诺的列表l
和一个函数f
,我可以这样做:
Promise.all(l.map(f));
困难的部分是,我需要按顺序映射每个元素 。也就是说,必须在下一个元素的开始之前解析第一个元素的映射。我想阻止任何并行性。
我知道如何做到这一点,我会给出答案,但我不确定这是一个好答案。
编辑:有些人认为,由于Javascript本身是单线程的,因此在Javascript中无法实现并行性。
请考虑以下代码:
const delay = t => new Promise(resolve => setTimeout(resolve, t));
mapAsync([3000, 2000, 1000], delay).then(n => console.log('beep: ' + n));
mapAsync()
的天真实现会导致“哔哔”每秒打印一次,持续三秒钟 - 数字按升序排列 - 但正确会有空格发出的声音越来越多,超过6秒,数字按降序排列。
对于一个更实际的例子,想象一个调用fetch()
并在数千个元素的数组上调用的函数。
进一步修改:
有人不相信我,所以here是小提琴。
答案 0 :(得分:1)
const mapAsync = (l, f) => new Promise((resolve, reject) => {
const results = [];
const recur = () => {
if (results.length < l.length) {
f(l[results.length]).then(v => {
results.push(v);
recur();
}).catch(reject);
} else {
resolve(results);
}
};
recur();
});
编辑: Tholle的评论让我更加优雅和(我希望)anti-pattern - 免费解决方案:
const mapAsync = (l, f) => {
const recur = index =>
index < l.length
? f(l[index]).then(car => recur(index + 1).then(cdr => [car].concat(cdr)))
: Promise.resolve([]);
return recur(0);
};
进一步编辑:
适当命名的Try-catch-finally最终建议使用reduce
进行更整洁的实现。欢迎进一步改进。
const mapAsync2 = (l, f) =>
l.reduce(
(promise, item) =>
promise.then(results =>
f(item).then(result => results.concat([result]))),
Promise.resolve([])
);
答案 1 :(得分:1)
不是自己编写逻辑,而是建议使用async.js。由于您正在处理promises,请使用promisified async-q库:https://www.npmjs.com/package/async-q(注意:文档在github上更容易阅读:https://github.com/dbushong/async-q)
您需要的是mapSeries
:
async.mapSeries(l,f).then(function (result) {
// result is guaranteed to be in the correct order
});
请注意,传递给f
的参数被硬编码为f(item, index, arr)
。如果你的函数接受不同的参数,你总是可以将它包装在另一个函数中以重新排序参数:
async.mapSeries(l,function(x,idx,l){
return f(x); // must return a promise
}).then(function (result) {
// result is guaranteed to be in the correct order
});
如果您的函数只接受一个参数,则不需要这样做。
您也可以使用基于async.js的原始回调:
async.mapSeries(l,function(x,idx,l){
function (cb) {
f(x).then(function(result){
cb(null, result); // pass result as second argument,
// first argument is error
});
}
},function (err, result) {
// result is guaranteed to be in the correct order
});
答案 2 :(得分:0)
由于您应该能够处理先前Promise的解决方案,因此您无法单独使用map()
。有一个很好的example of using reduce()
for sequencing Promises in an Google article。
reduce()
允许您使用前一项的Promise“链接”当前项目的Promise。要启动链,请将已解析的Promise作为初始值传递给reduce()
。
假设l
为输入数据,async()
以异步方式修改数据。它只会将输入数据乘以10。
var l = [1, 2, 3 ,4];
function async(data) {
console.log("call with ", data);
return new Promise((resolve, reject) => {
setTimeout(() => { console.log("resolve", data); resolve(data * 10); }, 1000);
});
}
这是相关代码(其功能是内联注释的)
// Reduce the inout data into a Promise chain
l.reduce(function(sequencePromise, inValue) {
/* For the first item sequencePromise will resolve with the value of the
* Promise.resolve() call passed to reduce(), for all other items it's
* the previous promise that was returned by the handler in the next line.
*/
return sequencePromise.then(function(responseValues) {
/* responseValues is an array, initially it's the empty value passed to
* reduce(), for subsequent calls it's the concat()enation result.
*
* Call async with the current inValue.
*/
return async(inValue).then((outValue) => {
/* and concat the outValue to the
* sequence array and return it. The next item will receive that new
* array as value to the resolver of sequencePromise.
*/
return responseValues.concat([outValue]);
});
});
}, Promise.resolve([]) /* Start with a resolved Promise */ ).then(function(responseValues){
console.log(responseValues);
});
控制台最终会记录
Array [ 10, 20, 30, 40 ]