Предпосылки:
У нас есть база данных, которую необходимо постоянно обновлять с помощью некоторого пакета данных, и приложение Node.JS, которое выполняет запросы в базе данных.
Эти действия выполняются следующим кодом:
try { await connection.transaction(); const promises = []; for (const elem of data) { promises.push(connection.query('updateElem', elem)); } await Promise.all(promises) await connection.commit(); } catch(error) { await connection.rollback(); }
Проблема:
Используя этот код, мы ожидаем следующего поведения: при сбое одного запроса транзакция откатывается, и данные должны оставаться согласованными. Но этот код приведет вас к одной странной ошибке: когда один запрос не выполняется, другие запросы частично откатываются (например, 6 из 10 запросов откатываются, но другие модифицируют данные, поэтому база данных становится несогласованной)
Причина:
Чтобы понять, как мы столкнулись с этой проблемой, нам нужно прочитать Документы MDN о Promise.all (). Он говорит нам:
Он отклоняет по причине первого обещания, которое отклоняет
И ни слова о прекращении исполнения других обещаний.
Итак, эту ошибку можно проиллюстрировать следующим кодом:
const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve(console.log('1')), 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(console.log('2')), 2000, 'two'); }); const p3 = new Promise((resolve, reject) => { setTimeout(() => resolve(console.log('3')), 3000, 'three'); }); const p4 = new Promise((resolve, reject) => { reject('reject'); }); Promise.all([p1, p2, p3, p4]).then(values => { console.log(values); }).catch(reason => { console.log(reason); });
И результат будет:
reject 1 2 3
Что-то подобное произошло, когда мы использовали Promise.all()
внутри транзакции:
- Транзакция началась;
- Запросы в очереди на выполнение;
- Не удалось выполнить один запрос;
- Откат транзакции;
- Некоторые медленные запросы выполняются после отката транзакции.
Решения:
- Спасибо за это Чаду Эллиотту: Начиная с [email protected], вы можете использовать
Promise.allSettled()
. Это не только дождется завершения всех обещаний, но и даст вам массив с результатом каждого обещания. Просто просмотрите результаты и убедитесь, что ни у одного из них нет статуса "отклонено". - Не лучшее решение, но оно работает: не используйте
Promise.all()
внутри переходов, используйте последовательное выполнение запроса.
P.S.
Если вы знаете лучшее решение этой проблемы, не стесняйтесь обращаться ко мне с комментариями к этому сообщению или по электронной почте [email protected]