Функция исполнителя, которую вы передаете new Promise
, выполняется немедленно , прежде чем будет возвращено новое обещание. Итак, когда вы делаете:
var p1 = new Promise(function(resolve, reject) {
setTimeout(() => resolve("first"), 5000);
});
... к тому времени, когда обещание назначено на p1
, setTimeout
уже уже был вызван и запланировал обратный вызов на пять секунд позже. Этот обратный вызов происходит независимо от того, слушаете ли вы разрешение обещания или нет, и это происходит независимо от того, слушаете ли вы разрешение с помощью ключевого слова await
или метода then
.
Таким образом, ваш код начинает три setTimeouts
сразу , а затем начинает ждать разрешения первого обещания и только затем ждет разрешения второго обещания (оно уже будет решено, так что это почти сразу ), а затем ждем третьего (опять же).
Чтобы ваш код выполнял эти setTimeout
вызовы только последовательно, когда истек предыдущий тайм-аут, вы не должны создавать новое обещание, пока не будет разрешено предыдущее обещание (используя более короткие тайм-ауты, чтобы избежать большого количества ожидания):
console.log("starting");
new Promise(function(resolve, reject) {
setTimeout(() => resolve("first"), 1000);
})
.then(result => {
console.log("(1) got " + result);
return new Promise(function(resolve, reject) {
setTimeout(() => resolve("second"), 500);
});
})
.then(result => {
console.log("(2) got " + result);
return new Promise(function(resolve, reject) {
setTimeout(() => resolve("third"), 100);
});
})
.then(result => {
console.log("(3) got " + result);
console.log("last to print");
});
Помните, что обещание ничего не делает и не меняет природу кода в исполнителе обещания. Все обещание дает средство наблюдения за результатом чего-либо (с действительно удобной комбинируемой семантикой).
Давайте выделим общие части этих трех обещаний в функцию:
function delay(ms, ...args) {
return new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
}
Тогда код становится немного понятнее:
function delay(ms, ...args) {
return new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
}
console.log("starting");
delay(1000, "first")
.then(result => {
console.log("(1) got " + result);
return delay(500, "second");
})
.then(result => {
console.log("(2) got " + result);
return delay(100, "third");
})
.then(result => {
console.log("(3) got " + result);
console.log("last to print");
});
Теперь давайте поместим это в функцию async
и используем await
:
function delay(ms, ...args) {
return new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
}
(async() => {
console.log("starting");
console.log("(1) got " + await delay(1000, "first"));
console.log("(2) got " + await delay(500, "second"));
console.log("(3) got " + await delay(100, "third"));
console.log("last to print");
})();
Обещания делают возможным этот синтаксис, стандартизируя то, как мы наблюдаем асинхронные процессы.
Повторное редактирование:
1: если функция-исполнитель, переданная новому Promise, выполняется немедленно, до того, как будет возвращено новое обещание, то почему здесь обещания разрешаются () синхронно) сначала и после выполнения setTimeouts
(асинхронно)?
Этот вопрос состоит из двух частей:
A) "... почему здесь обещания разрешаются () синхронно) первыми ..."
B) "... почему здесь обещания разрешаются ... после выполнения setTimeouts
(асинхронно)"
Ответ (A) таков: хотя вы разрешаете их синхронно, then
всегда вызывает свой обратный вызов асинхронно. Это одна из гарантий, которые обещают предоставить. Вы решаете p1
(в этом редактировании), прежде чем функция исполнителя возвращается. Но то, как вы наблюдаете разрешения, гарантирует, что вы соблюдаете разрешения по порядку, потому что вы не начинаете наблюдать p2
до тех пор, пока p1
не разрешится, и тогда вы не начнете наблюдать p3
до разрешения p2
.
Ответ на (B) таков: они этого не делают, вы решаете их синхронно, а затем наблюдаете эти разрешения асинхронно, и, поскольку они уже решены, это происходит очень быстро; позже запускаются обратные вызовы таймера. Давайте посмотрим, как вы создаете p1
в этом редактировании:
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => console.log("first"), 5000);
resolve("first resolved")
});
Что там происходит:
new Promise
вызывается
- Вызывает функцию исполнителя
- Функция исполнителя вызывает
setTimeout
, чтобы запланировать обратный вызов
- Вы немедленно разрешаете обещание с
"first resolved"
new Promise
возвращается, и разрешенное обещание присваивается p1
- Позже происходит тайм-аут, и вы выводите
"first"
на консоль
Тогда позже вы делаете:
p1.then((val) => {
console.log("(1)", val)
return p2
})
// ...
Поскольку then
всегда вызывает свой обратный вызов асинхронно, это происходит асинхронно & mdash; но очень скоро , потому что обещание уже выполнено.
Поэтому, когда вы запускаете этот код, вы видите, что все три обещания разрешаются за до первого setTimeout
обратного вызова & mdash; потому что обещания не ждут обратного вызова setTimeout
.
Вам может быть интересно, почему вы видите свой последний then
обратный вызов, запущенный до того, как вы увидите "third"
в консоли, поскольку разрешения обещаний и console.log("third")
происходят асинхронно, но очень скоро ( поскольку это setTimeout(..., 0)
и все обещания предварительно разрешены): Ответ заключается в том, что разрешения обещаний равны микрозадачам и setTimeout
, равным макрозадачам (или просто "задачам") , Все микрозадачи, выполняемые расписаниями задач, выполняются сразу же после завершения этой задачи (и все запланированные ими микрозадачи затем выполняются), прежде чем следующая задача будет взята из очереди задач. Итак, задача, выполняющая ваш скрипт, делает это:
- Запланирует задачу для обратного вызова
setTimeout
1139 *
- Назначает микрозадачу для вызова
p1
then
callback
- Когда задача заканчивается, ее микрозадачи обрабатываются:
- Первый обработчик
then
запускается, планируя выполнение микрозадачи для запуска второго обработчика then
- Второй обработчик
then
запускает и планирует микро-задачу для вызова третьего обработчика then
- Etc. пока все
then
обработчики не запустятся
- Следующее задание берется из очереди заданий. Вероятно, это обратный вызов
setTimeout
для p3
, поэтому он запускается и в консоли появляется "third"
- Возвращаемое значение против разрешения обещания:
Часть, которую вы задали в вопросе, не имеет смысла для меня, но ваш комментарий по этому поводу:
Я прочитал, что возвращение значения или разрешение обещания одно и то же ...
Вероятно, вы прочитали, что возвращение значения из then
или catch
- это то же самое, что возвращение разрешенного обещания из then
или catch
, Это потому, что then
и catch
создают и возвращают новые обещания при вызове, и если их обратные вызовы возвращают простое (не обещающее) значение, они разрешают созданное обещание с этим значением; если обратный вызов возвращает обещание, они разрешают или отклоняют созданное обещание в зависимости от того, разрешает или отклоняет это обещание.
Так, например:
.then(() => {
return 42;
})
и
.then(() => {
return new Promise(resolve => resolve(42));
})
имеют тот же конечный результат (но второй менее эффективен).
В пределах then
или catch
обратного вызова:
- Возврат без обещания разрешает обещание
then
/ catch
, созданное с этим значением
- Бросок ошибки (
throw ...
) отклоняет это обещание со значением, которое вы бросаете
- При возврате обещания
then
/ catch
обещание разрешается или отклоняется на основании обещания, которое обратный вызов возвращает