Предпосылки:
У нас есть база данных, которую необходимо постоянно обновлять с помощью некоторого пакета данных, и приложение 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]