JavaScript数组异步映射

227 阅读2分钟

在前端业务中,我们有时会对一个数组的所有元素进行异步处理(异步映射,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,真香!

参考:JavaScript ES6 promise for loop