Предпосылки:

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

  1. Транзакция началась;
  2. Запросы в очереди на выполнение;
  3. Не удалось выполнить один запрос;
  4. Откат транзакции;
  5. Некоторые медленные запросы выполняются после отката транзакции.

Решения:

  1. Спасибо за это Чаду Эллиотту: Начиная с [email protected], вы можете использовать Promise.allSettled(). Это не только дождется завершения всех обещаний, но и даст вам массив с результатом каждого обещания. Просто просмотрите результаты и убедитесь, что ни у одного из них нет статуса "отклонено".
  2. Не лучшее решение, но оно работает: не используйте Promise.all() внутри переходов, используйте последовательное выполнение запроса.

P.S.

Если вы знаете лучшее решение этой проблемы, не стесняйтесь обращаться ко мне с комментариями к этому сообщению или по электронной почте [email protected]