在前端业务中,我们有时会对一个数组的所有元素进行异步处理(异步映射,Asynchronous Mapping),所以不能简单地遍历。一种有效的原生JavaScript方法是使用递归进行异步调用,将下一个元素的异步映射作为当前元素异步映射的回调(Callback):
// 递归函数
let recurMap = (arr, map) => {
// 如果数组不为空,继续递归
if (arr.length > 0) {
// 数组左移并获取头元素
let x = arr.shift();
// 定义递归子函数
sub = () => {
recurMap(arr, callback);
}
// 进行异步映射,并插入递归子函数
map(x, sub);
}
};
// 异步映射函数,参数resolve提供递归“钩子”
let map1 = (x, resolve) => {
setTimeout(() => {
console.log(x);
resolve();
}, 1000);
}
let arr1 = [1, 2, 3];
recurMap(arr1, map1);
结果为每秒输出一个数组元素。
进一步地,我们给递归函数添加一个next参数,可以把多个递归任务(多种异步映射函数)串联起来:
let recurMap = (arr, map, next) => {
if (arr.length > 0) {
let x = arr.shift();
sub = () => {
// 相应地,传递next给子递归函数
recurMap(arr, callback, next);
}
map(x, sub);
} else if (next) {
// 如果该递归任务已完成,且有下一个任务,则进行回调
next();
}
};
let map1 = (x, resolve) => {
setTimeout(() => {
console.log(x);
resolve();
}, 1000);
}
let map2 = (x, resolve) => {
setTimeout(() => {
console.log(x + 10);
resolve();
}, 1000);
}
let arr1 = [1, 2, 3]
let arr2 = [2, 3, 4]
// 链式递归任务
recurMap(arr1, map1, () => {
recurMap(arr2, map2);
});
我们试一下用Promise如何实现异步映射(暂不考虑reject):
let promiseMap = (arr, map) => {
let p = Promise.resolve();
for (let x of arr) {
// map里每次resolve时,p更新为一个新的Promise,负责下一次异步映射
p = p.then(_ => new Promise(resolve => {
// 传递resolve“钩子”给map
map(x, resolve);
}));
}
}
// map1, arr1同上
promiseMap(arr1, map1);
我们可以用Array.prototype.reduce写得更neat一些:
let promiseMap = (arr, map) => {
arr.reduce((p, x) =>
// 每次更新作为accumulator的p
p.then(_ => new Promise(resolve => {
map(x, resolve);
}))
// reduce初始值
, Promise.resolve());
}
研究了半天,然后你突然想到async/await:
let asyncMap = async (arr, map) => {
for (let x of arr) {
await new Promise(resolve => map(x, resolve));
}
}
不愧是Modern JS,真香!