背景

前几天,一个朋友叫我帮他排查一个 BUG ,问题就是列表渲染不出来,拿不到数据,他的代码如下

const data = []
const urls = ['api1', 'api2']
urls.forEach(async (url) => {
  const v = await request(url)
  data.push(v)
})
this.data = data
// 一直是空数组
console.log(this.data)

他说这个数据一直查不到。其实大部分同学一看到这个代码就知道为啥没数据了吧?但是我不想马上就跟他说错在哪,我让他先去看看 forEach的实现原理是什么

forEach 实现原理

forEach的实现原理非常简单,如下:

Array.prototype.myForEach = function(callback) {  
  for (let i = 0; i < this.length; i++) {  
    callback(this[i], i, this);  
  }  
}

所以说,在 forEach中使用 async/await 是不可取的,因为它的 callback 是并行执行的,并没有排队效果,那么 data 自然就获取不到,因为 async 是异步的

解决问题

所以需要使用 Promise.all 即可

const urls = ['api1', 'api2']
const data = await Promise.all
  urls.map(url => request(url))
)
this.data = data

不要过度依赖 forEach

我看到很多人都基本只用 forEach,但是它是有很多缺点的

所以不要过度依赖它

中断、排队 可以用 for

其实直接用 for 循环也可以实现排队、中断的效果,这都是 forEach做不到的

data.forEach(item => {
  // 没有效果
  if (xxx) break;
})
 
for (let item of data) {
  // 有效果
  if (xxx) break;
}
data.forEach(async item => {
  // 没有效果
  await item()
})
 
for (let item of data) {
  // 有效果
  await item()
}

合理使用 filter、map、reduce

如果你需要过滤一个数组,可以使用 filter

const result = []
data.forEach(item => {
  if (item.flag) {
    result.push(item)
  }
})
 
// 用 filter
const result = 
data.filter(({ flag }) => flag)

如果你需要格式化数组数据,你可以使用 map

const result = []
data.forEach(item => {
  result.push({
    name: item.name,
    age: `${item.age}岁`
  })
})
 
// 使用 map
const result = 
data.map(item =({
  name: item.name,
  age: `${item.age}岁`
}))

如果你需要累计,你可以使用 reduce

const sum = 0
nums.forEach(num => {
  sum += num
})
 
// 使用 reduce
const sum = 
nums.reduce((pre, next) => {
  const v = pre + next
  return v
}, 0)