阅读 3598

当 forEach 遇上了 async 与 await

前言

最近在写一个小项目练手 Vue3koa ,在用 koa 写后台路由接口的时候,遇到了这么一个问题:

当前有一个存有用户 id 的数组,例如:[1, 2, 3, ...],我需要使用 sql 语句查询每一个 id 对应的用户的用户名。下面写一下我最初的伪代码:

// id 数组
const arr = [1, 2, 3, ...]
const infoList = new Array(arr.length)
arr.forEach((id, index) => {
  const sql = `select name from users where id='${id}';`
  const res = exec(sql) // 这里有问题
  infoList[index].id = id
  infoList[index].name = res
})
复制代码

最初的伪代码是这样的,解释一下,sql 语句你可以跳过,exec 是我封装的 mysql 的执行 sql 语句的方法。这个操作应该是异步的。所以我们的 res 是获取不到值的。

需要给 exec 前面加上 await 来执行它,这样当前的函数就必须是一个 async 函数了,所以代码可以写成下面这样:

const arr = [1, 2, 3, ...]
arr.forEach(async (id, index) => {
  const sql = `select name from users where id='${id}';`
  const res = await exec(sql)
  infoList[index].id = id
  infoList[index].name = res
})
复制代码

这样看起来并没有什么不妥,但是当我这样写下来执行代码的时候就发现了问题,infoList 的每一项都是空的。理由只有一个:forEach 循环没有等待 await 的执行。

发现了这一问题后就关于 forEachasync 函数一起使用时的问题以及原理记录此文。

forEach

发现问题并且找到问题的根源后,我们就先来看一下 forEach 的原理,我尝试着根据 forEach 的用法,简单实现一下它:

Array.prototype.myForEach = function(callback, thisArg) {
  for (let i = 0; i < this.length; i ++) {
    callback.call(thisArg, this[i], i, this)
  }
}
复制代码

这样我们就可以使用我们写的 forEach 方法了:

const arr = [1, 2, 3]

arr.myForEach((item, index, array) => {
  console.log(item, index)
})
复制代码

正如所料是可以运行的,相信到这里问题也就迎刃而解了。

使用我们手写的 myForEach 实现以前的用例:

// id 数组
const arr = [1, 2, 3, ...]
arr.myForEach(async (id, index) => {
  const sql = `select name from users where id='${id}';`
  const res = await exec(sql)
  infoList[index].id = id
  infoList[index].name = res
})
复制代码

可以看到 myForEach 的参数 callback 是一个异步函数,但是在内部进行调用的时候并没有使用 await 关键字来等待异步执行的结果,而是直接进行循坏,所以当然不会拿到结果。

解决

找到了问题后我们尝试着解决它,其实方法也很简单,就只需要让 callback 函数不要立即执行就好,而是等待异步任务的状态改变后执行,所以改造我们的 myForEach 函数:

Array.prototype.myForEach = async function(callback, thisArg) {
  for (let i = 0; i < this.length; i ++) {
    await callback.call(thisArg, this[i], i, this)
  }
}
复制代码

这样就可以确保每一次的 callback 函数内的代码是都可以准确的拿到值啦。

其他解决方式

当然除了改写原生的 forEach 方法还可以使用其他的循环结构,例如普通的 for 循环、for ... in ... for ... of ... ,这些循环结构都会在本身得异步函数作用域中执行,可以在其内部直接添加 await 关键字获取到值。

文章分类
前端
文章标签