использование forEach и async / await ведет себя по-разному для узла и Jest - PullRequest
0 голосов
/ 18 февраля 2019

У меня есть функция, которая записывает данные в mongodb, например так:

const writeToDB = async (db, data) => {
  const dataKeys = Object.keys(data)
  dataKeys.forEach(async key => db.collection(key).insertMany(data[key]))
}

Это прекрасно работает, если я запускаю его в скрипте узла.Но когда я попытался использовать его в beforeAll Jest, я получил эту асинхронную ошибку от Jest:

Jest не завершил свою работу через одну секунду после завершения тестового прогона.Обычно это означает, что существуют асинхронные операции, которые не были остановлены в ваших тестах.

после некоторого устранения неполадок я обнаружил, что forEach вызывал проблему.Использование цикла for решило эту проблему:

const writeToDB = async (db, data) => {
  const dataKeys = Object.keys(data)
  for (const key of dataKeys) {
    await db.collection(key).insertMany(data[key])
  }
}

В поисках этой проблемы я наткнулся на эту статью: https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404

Объяснение здесь имело смысл, но у меня остались некоторые вопросы:

  • Согласно статье, он не должен был работать даже в скрипте узла.Как получилось?
  • Почему выполнение его в узле отличается от запуска в Jest?

edit

После прочтения всехкомментарии, я понимаю, что мой первый вопрос был чепухой.Обычно я присваиваю переменную результат асинхронной функции, и если я не укажу await, произойдет неопределенная ошибка.Но это не тот случай, поэтому сценарий завершается нормально, и запись в БД происходит одновременно в фоновом режиме.

Ответы [ 2 ]

0 голосов
/ 18 февраля 2019

Существующий ответ уже подробно объясняет, почему forEach не следует использовать с обещаниями так, как он используется.forEach обратный вызов не учитывает возвращенные обещания и разрывает цепочку обещаний.async..await необходимо использовать с for..of для оценки посылок последовательно или с Promise.all и map для параллельной оценки.

Jest поддерживает обещания и ожидает, что обещание, возвращаемое из асинхронной функции(it и т. Д.) Означает, что асинхронный процесс, который произошел в этой функции, завершился.

Как только Jest завершает тест, он проверяет, есть ли открытые дескрипторы, которые препятствуют выходу Node.Поскольку обещания не возвращались и не связывались Jest, процессы, которые они представляют, не позволяют Jest завершить процесс тестирования.

Эта проблема отображается в сообщении об ошибке:

Jest не завершилсячерез одну секунду после завершения теста.

Обычно это означает, что существуют асинхронные операции, которые не были остановлены в ваших тестах.Для устранения этой проблемы попробуйте запустить Jest с --detectOpenHandles.

0 голосов
/ 18 февраля 2019

Асинхронные функции работают даже в тех контекстах, которые не await или не вызывают .then() для них, то есть я определенно могу сделать это:

async function foo() {
  // async calls here
}

foo(); // no await or .then().

Это означает, что вы не можете ждать операциичтобы завершить, вы не можете использовать это значение, и, что хуже всего, вы не можете перехватить или исправить любые асинхронные ошибки, которые могут быть выброшены (или отклонены, если мы будем точны)

Основное различие заключается вчто .forEach() не заботится и не ждет завершения операций перед вызовом следующей (поскольку асинхронные функции return немедленно), тогда как ваш вызов for..of использует await для ожидания завершения каждой операции перед перемещениемк следующему.

Ваш первый пример .forEach будет примерно эквивалентен нижнему, если вы удалите await из вызова внутри цикла.

В результате получается, чтоВаш первый пример возвращает Обещание, которое разрешается немедленно, а не после того, как все ваши вызовы БД были завершены, поэтому тест ожидает завершения операций, но это не так.Во втором примере вы правильно await завершаете все вызовы до завершения асинхронной функции, поэтому возвращаемое обещание будет разрешено только после того, как все вызовы разрешатся, во-первых.


На этомобратите внимание, что эти два примера не эквивалентны, поскольку первый будет вызывать insertMany один за другим, не дожидаясь их завершения, в результате чего вызовы БД будут выполняться параллельно.

Если вы хотите сохранить это поведение, но по-прежнему возвращаете правильное Обещание, которое ожидает завершения всего, вы должны использовать [].map() вместо [].forEach:

const writeToDB = async (db, data) => {
  const dataKeys = Object.keys(data)
  const allPromises = dataKeys.map(async key =>
    await db.collection(key).insertMany(data[key])) // run all calls in parallel
  return await Promise.all(allPromises); // wait for them all to finish
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...