Когда forEach встречается с async и await

JavaScript

предисловие

Недавно я писал небольшой проект, чтобы потренировать свои руки.Vue3а такжеkoa,С использованием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исполнение.

Обнаружив эту проблему,forEachа такжеasyncПроблемы и принципы при совместном использовании функций описаны в этой статье.

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Ключевое слово получает значение.