У меня есть функция, которая записывает данные в mongodb, например:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
dataKeys.forEach(async key => db.collection(key).insertMany(data[key]))
}
Это отлично работает, если я запускаю его в сценарии узла. Но когда я попытался использовать его в Jest beforeAll, я получил эту асинхронную ошибку от Jest:
Jest did not exit one second after the test run has completed. This usually means that there are asynchronous operations that weren't stopped in your tests.
после некоторого устранения неполадок я обнаружил, что 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 жалуется, потому что процесс все еще выполняется после завершения теста — процесс не может завершиться, пока все промисы не будут выполнены. Node не возражает, потому что такой проверки нет, он просто поддерживает процесс. Вы также можете написать await Promise.all(dataKeys.map((key) => db.collection(key).insertMany(data[key]));, что позволяет выполнять параллельные операции.
@jonrsharpe, но почему процесс завершился правильно, когда я запустил его в обычном сценарии узла?
Почему бы и нет? Процесс продолжается — это то, на что Jest жалуется, если вы читаете сообщение об ошибке, что в фоновом режиме все еще что-то происходит, хотя все тесты завершены.
Почему обратный вызов forEach асинхронный, если вы не используете Ждите? Вы что-то упускаете здесь.
@MadWard, как я понял из правила eslint без возврата, ожидание в этом случае не требуется
Что именно вы подразумеваете под «Это отлично работает, если я запускаю его в сценарии узла.». Как это работает "хорошо", что вы тестировали? Вы удивлены, что он запускал все прошивки?
Возможный дубликат stackoverflow.com/questions/37576685/…



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Асинхронные функции работают даже в контекстах, которые не вызывают их await или не вызывают .then(), то есть я точно могу это сделать:
async function foo() {
// async calls here
}
foo(); // no await or .then().
Это означает, что вы не можете дождаться завершения операции, вы не можете использовать значение и, что хуже всего, вы не можете поймать или исправить любые асинхронные ошибки, которые могут быть вызваны (или отклонены, если быть точным).
Основное отличие состоит в том, что .forEach() не заботится о завершении операций или ожидает их завершения перед вызовом следующей (поскольку асинхронные функции return выполняются немедленно), тогда как ваш вызов for..of использует await для ожидания завершения каждой операции, прежде чем перейти к следующей. .
Ваш первый пример .forEach был бы примерно эквивалентен нижнему, если бы вы удалили await из вызова внутри цикла.
В результате ваш первый пример возвращает обещание, которое разрешается немедленно, а не после завершения всех ваших вызовов БД, поэтому тест ожидает завершения операций, но это не так. Во втором примере вы должны правильно await завершить все вызовы до завершения асинхронной функции, поэтому return Promise будет разрешаться только после того, как все вызовы разрешатся сами собой.
В этой заметке два примера не эквивалентны, потому что первый будет вызывать 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
}
Думаю, вы ответили на мой первый вопрос. Дело не в том, что node выдаст ошибку, если forEach не ждет, ему просто все равно, и он выполняется параллельно, как вы упомянули. Но почему шутка волнует? как он обнаруживает, что обещания не были ожидаемы?
Спасибо, я принял ответ Эстуса, потому что он ответил на второй вопрос. Но это моя ошибка, я не должен был задавать два вопроса сразу.
@devboell Ну, Jest - это средство запуска тестов, оно предназначено для поиска этих вещей, потому что они указывают на ошибки :)
Существующий ответ уже подробно объясняет, почему forEach не следует использовать с обещаниями так, как он используется. forEach обратный вызов не учитывает возвращенные обещания и разрывает цепочку обещаний. async..await необходимо использовать с for..of для последовательной оценки обещаний или с Promise.all и map для параллельной оценки.
Jest поддерживает обещания и ожидает, что обещание, возвращаемое асинхронной функцией (it и т. д.), означает, что асинхронный процесс, который произошел в этой функции, завершен.
Как только Jest завершает тест, он проверяет, есть ли открытые дескрипторы, препятствующие выходу Node. Поскольку обещания не возвращались и не связывались Jest, процессы, которые они представляли, не позволяли Jest завершить процесс тестирования.
Эта проблема представлена указанным сообщением об ошибке:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
forEachнеawaitдляasyncобратного вызова для урегулирования.