背景
前几天,一个朋友叫我帮他排查一个 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)