Promise.all сначала разрешает последнее обещание

Я изучаю обещания и зашел в тупик. Итак, у меня есть эти 2 обещания, где первое извлекает данные из базы данных и возвращает обещание и обновляет объект data, а последнее — обещание, которое я сделал, потому что окончательный объект данных зависит от разрешенных значений из первого обещания.

Допустим, data — это объект, который я буду обновлять с помощью разрешенных значений промисов из первого промиса. Итак, вот как это выглядит:

let promises = [];
let data = {};

promises.push(
  this.service1.getFirstPromiseData()
    .then(result => {
       data = {
         ...result
       }
    })
);

promises.push(
  this.getSecondPromiseData()
    .then(result => {
       data = {
         ...result,
         ...data
       }
    })
);

await Promise.all(promises);

getSecondPromiseData — это мое обещание обработать некоторые пользовательские данные данными, полученными из первого обещания. Итак, вот как это выглядит:

getThirdPromiseData() {
  return new Promise((resolve, reject) => {
      resolve({ name: 'John Doe' });      
  });
}

Прямо сейчас, если я запишу значение объекта data внутри then второго обещания, я получу пустой объект. Кроме того, если я регистрирую значение result второго обещания, похоже, что оно разрешается намного раньше, чем первое обещание, поскольку оно регистрирует значение сразу.

Мне нужно, чтобы первое обещание было разрешено, а объект data обновился до того, как будет разрешено второе обещание.

Как мне это сделать?

Нет никакой гарантии, в каком порядке будут выполняться промисы. Это зависит от того, что они делают и когда они заканчивают это делать. Это не то, как вы должны накапливать результаты. Каждое обещание должно возвращать свои данные, тогда Promise.all вернет массив всех результатов, и вы можете объединить этот окончательный массив в один объект.

deceze 20.02.2023 11:48

@deceze Я думал, что promise.all разрешается по порядку.

s.khan 20.02.2023 11:50

Нет. Promise.all разрешается, когда разрешаются все внутренние обещания. Промисы начинают выполняться, как только вы их создаете. Promise.all не контролирует внутренние обещания, она просто ждет их разрешения.

deceze 20.02.2023 11:55

@deceze - урегулирование, а не разрешение. :-)

T.J. Crowder 20.02.2023 11:56

@TJC Potaytoh, tomahtoh… ;)

deceze 20.02.2023 11:57

@deceze - я бы сказал, что нет. Обещание может быть выполнено, но все еще находится в ожидании (и часто так и есть). Одна из моих второстепенных миссий в жизни — помочь людям понять разницу, когда я сталкиваюсь с людьми, путающими решение с урегулированием или исполнением. :-)

T.J. Crowder 20.02.2023 12:34

@TJC Достаточно справедливо, но в данном случае это мало что меняет, поэтому я попытался повторно использовать существующую терминологию. Но да, если вы действительно хотите вдаваться в подробности, различие имеет значение.

deceze 20.02.2023 12:50

@deceze - :-) (FWIW, @TJC не уведомляет меня. Что меня удивляет...)

T.J. Crowder 20.02.2023 12:56

@TJC Ммм, я тоже. Мог бы поклясться, что раньше.

deceze 20.02.2023 13:01
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
9
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

когда вы «ждете Promise.all», вы запускаете все промисы параллельно, поэтому нет гарантированного порядка их выполнения.

Если вам нужно упорядочить вызовы, вам нужно будет «ждать» каждого обещания, прежде чем запускать следующее.

например

let data = {};

await this.service1.getFirstPromiseData()
    .then(result => {
       data = {
         ...result
       }
    });

await this.getSecondPromiseData()
    .then(result => {
       data = {
         ...result,
         ...data
       }
    });

Это правильный синтаксис, использующий await with then?

s.khan 20.02.2023 12:07

@ s.khan - Это правильный синтаксис, но неправильный в широком смысле. Нет смысла комбинировать async/await с явным подключением обратных вызовов обещаний через then или catch (но в некоторых ограниченных случаях это сводится к вопросу стиля). Но создание data, как указано выше, не является хорошей практикой; просто создайте его один раз в конце.

T.J. Crowder 20.02.2023 12:23
Ответ принят как подходящий

Promise.all сначала разрешает последнее обещание

Promise.all вообще не разрешает обещания, которые вы передаете. Обещания — это всего лишь средства сообщения и наблюдения за результатом асинхронного действия. Действия, о которых сообщают ваши два обещания, уже выполняются к моменту вызова Promise.all. Promise.all просто наблюдает за ними и ждет, пока они осядут. Вот и все. Он вообще их не запускает и не запускает.

Что касается относительного времени урегулирования (не «разрешения») этих обещаний: это зависит от того, что делает действие, о котором они сообщают. Если второй оседает быстрее, чем первый, то именно так и происходит. Это не имеет ничего общего с порядком промисов в вашем массиве.


В комментарии, отвечающем на комментарий к вопросу, который вы сказали:

Я думал promise.all решает по порядку.

Если все обещания, которые вы даете Promise.all, выполнены, Promise.all выполняет свое обещание с массивом этих значений выполнения и гарантирует, что элементы в массиве находятся в том же порядке, что и обещания, которые вы ему даете. Но это не имеет никакого отношения к тому, когда обещания устанавливаются, а только к тому, в каком порядке они находятся в массиве.

Вот пример этого: Обратите внимание, что независимо от того, как часто вы запускаете этот код, массив результатов всегда ["A", "B", "C"]:

function example(delay, value) {
    return new Promise((resolve) => {
        setTimeout(
            () => {
                console.info(`Fulfilling with ${value}`);
                resolve(value);
            },
            delay
        );
    });
}

// Waits a full second before fulfilling with "A"
const promiseA = example(1000, "A");

// Waits almost not time before fulfilling with "B"
const promiseB = example(0, "B");

// Waits half a second before fulfilling with "C"
const promiseC = example(500, "C");

// But the fulfillment value array from Promise.all is
// always in the order of the promises you give it
Promise.all([
    promiseA,
    promiseB,
    promiseC,
])
.then((result) => {
    console.info(result);
});

Причина, по которой Promise.all дает вам результаты по порядку, заключается в том, что вы знаете, какое обещание дало какой результат. Это не имеет ничего общего со временем.


Относительно «урегулирования» и «разрешения» (и другой терминологии обещаний) см. мой пост в блоге здесь.


В комментарии вы спросили:

Эй, как вы думаете, я могу обновить свой код, чтобы добиться того, чего я хочу?

Вы сказали, что две вещи могут работать параллельно, но вам нужны оба результата, прежде чем вы сможете построить data. Это именно то, для чего предназначен Promise.all, так что вы на верном пути. Вот как это сделать:

// Run the actions in parallel, wait until you have both results
const [firstResult, secondResult] = await Promise.all([
    this.service1.getFirstPromiseData(),
    this.getSecondPromiseData(),
]);
// ...use `firstResult` and `secondResult` here...

В вашем вопросе вы использовали два результата для создания одного объекта. Если это действительно то, что вы хотите, то:

const data = {
    ...firstResult,
    ...secondResult,
};

Но имейте в виду, что использование результатов таким образом означает, что любые свойства в secondResult, которые имеют те же ключи, что и свойства в firstResult, будут «выигрывать» над теми, что в firstResult. Например, если firstResult является объектом, подобным {a: 1, b: 2, c: 3}, а secondResult является объектом, подобным {a: "one"}, в конце data будет объект, который имеет {a: "one", b: 2, c: 3} — свойство a из this.service1.getFirstPromiseData() отбрасывается.

Эй, как вы думаете, я могу обновить свой код, чтобы добиться того, чего я хочу?

s.khan 20.02.2023 12:05

@ s.khan - Это зависит от того, действительно неясно, чего вы хотите. Могут ли this.service1.getFirstPromiseData() и this.getSecondPromiseData() работать параллельно? Если да, я добавил к ответу, но...

T.J. Crowder 20.02.2023 12:16

Они могут работать параллельно, но мне понадобятся данные первого для обработки данных последнего.

s.khan 20.02.2023 12:48

@s.khan - Хорошо, тогда мой пример в конце ответа правильный. Этот конкретный пример объединяет их с использованием синтаксиса распространения, как в вашем вопросе, но вы можете комбинировать их другими способами. Ключевой бит — это const [firstResult, secondResult] = await Promise.All(/*...*/);.

T.J. Crowder 20.02.2023 12:50

Другие вопросы по теме