【javascript】循环中使用异步函数 修改数组元素的一些坑

808 阅读2分钟

在接触到闭包这个概念的时候,应该都见过下面这个例子,用闭包来解决i值错误的问题。

for (var i = 0; i <= 3; i++) {
  (function (i) {
    setTimeout(function() {
      console.log(i)
    })
  })(i)
}

但是在循环中调用异步函数,修改数组元素属性时,出现了问题

先给出数组和两个函数

let arr1 = [{num: 1}, {num: 2}, {num: 3}]

function asyncFun(index) {
  setTimeout(() => {
    return index;
  }, 500);
}

function asyncFunWithPromise(index) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(index)
      return index;
    }, 500);
  })
}

先上ES5加闭包,运行中直接报错。

for (var i = 0; i <= arr1.length; i++) {
  (function (i) {
    console.log(i)
    arr1[i].result = asyncFun(i) // Cannot set property 'result' of undefined
  })(i)
}
//  等asyncFun函数返回时,循环中arr1[i]这个引用已随着循环结束消失

用ES6也是徒劳的,不报错,但是结果如下,这里也体现了let的一些特性。。。有空再深入琢磨

for (let index = 0; index < arr1.length; index++) {
  console.log(index)
  const currentItem = arr1[index];
  currentItem.result = asyncFun(currentItem.num)
}
/* [
  { num: 1, result: undefined },
  { num: 2, result: undefined },
  { num: 3, result: undefined }
] */

用 async + await能解决这个问题,但整个循环相当于串行执行了,特别慢。

(async () => {
  for (let index = 0; index < arr1.length; index++) {
    console.log(index)
    const currentItem = arr1[index];
    currentItem.result = await asyncFunWithPromise(currentItem.num)
  }
})()
// 返回值
// [ { num: 1, result: 0 }, { num: 2, result: 1 }, { num: 3, result: 2 } ]

forEach + async await能做到并行执行异步函数,但是无法获知异步函数何时全部返回

arr1.forEach(async (listItem, index) => {
  console.log(index)
  listItem.result = await asyncFunWithPromise(index)
});
console.log('done')
console.log(arr1) // [ { num: 1 }, { num: 2 }, { num: 3 } ],此处异步函数未结束

最后加入Promise.all,判断异步函数全部返回,高效。

let heighEfficiency = () => {
  return new Promise((resolve, reject) => {
    const promiseArr = []
    arr1.forEach(async (listItem, index) => {
      promiseArr.push(asyncFunWithPromise(index))
      listItem.result = await promiseArr[index]
    });
    Promise.all(promiseArr).then(() => {
      resolve()
    }).catch(() => {
      reject()
    })
  })
}

heighEfficiency().then(() => {
  console.log(arr1)
})

// [ { num: 1, result: 0 }, { num: 2, result: 1 }, { num: 3, result: 2 } ]

屈服了,把数组元素传出去了。。。毕竟map,没办法

const functionWithPromise = item => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      item.result = item.num + ':async done'
      resolve(item)
      return;
    }, 500);
  })
}

const getData = () => {
  return Promise.all(arr1.map(item => functionWithPromise(item)))
}

getData().then(data => {
  console.log(data)
})

这个实现挺别扭的,应该有更佳的实现。求大佬帮忙。