Я изучаю обещания и зашел в тупик. Итак, у меня есть эти 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
обновился до того, как будет разрешено второе обещание.
Как мне это сделать?
@deceze Я думал, что promise.all разрешается по порядку.
Нет. Promise.all
разрешается, когда разрешаются все внутренние обещания. Промисы начинают выполняться, как только вы их создаете. Promise.all
не контролирует внутренние обещания, она просто ждет их разрешения.
@deceze - урегулирование, а не разрешение. :-)
@TJC Potaytoh, tomahtoh… ;)
@deceze - я бы сказал, что нет. Обещание может быть выполнено, но все еще находится в ожидании (и часто так и есть). Одна из моих второстепенных миссий в жизни — помочь людям понять разницу, когда я сталкиваюсь с людьми, путающими решение с урегулированием или исполнением. :-)
@TJC Достаточно справедливо, но в данном случае это мало что меняет, поэтому я попытался повторно использовать существующую терминологию. Но да, если вы действительно хотите вдаваться в подробности, различие имеет значение.
@deceze - :-) (FWIW, @TJC
не уведомляет меня. Что меня удивляет...)
@TJC Ммм, я тоже. Мог бы поклясться, что раньше.
когда вы «ждете 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 - Это правильный синтаксис, но неправильный в широком смысле. Нет смысла комбинировать async
/await
с явным подключением обратных вызовов обещаний через then
или catch
(но в некоторых ограниченных случаях это сводится к вопросу стиля). Но создание data
, как указано выше, не является хорошей практикой; просто создайте его один раз в конце.
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 - Это зависит от того, действительно неясно, чего вы хотите. Могут ли this.service1.getFirstPromiseData()
и this.getSecondPromiseData()
работать параллельно? Если да, я добавил к ответу, но...
Они могут работать параллельно, но мне понадобятся данные первого для обработки данных последнего.
@s.khan - Хорошо, тогда мой пример в конце ответа правильный. Этот конкретный пример объединяет их с использованием синтаксиса распространения, как в вашем вопросе, но вы можете комбинировать их другими способами. Ключевой бит — это const [firstResult, secondResult] = await Promise.All(/*...*/);
.
Нет никакой гарантии, в каком порядке будут выполняться промисы. Это зависит от того, что они делают и когда они заканчивают это делать. Это не то, как вы должны накапливать результаты. Каждое обещание должно возвращать свои данные, тогда
Promise.all
вернет массив всех результатов, и вы можете объединить этот окончательный массив в один объект.