Все три из этих параметров запускают код в основном потоке и блокируют событие l oop. Существует небольшая разница во времени, когда они запускают код while
l oop и когда они блокируют событие l oop, что приведет к разнице в том, когда они запускаются по сравнению с некоторыми из ваших сообщений консоли.
Первая и вторая опции блокируют событие l oop немедленно.
Третья опция блокирует событие l oop, начиная со следующего тика - это когда Promise.resolve().then()
вызывает переданный вами обратный вызов на .then()
(на следующем тике).
Первый вариант - это просто чистый синхронный код. Неудивительно, что он немедленно блокирует событие l oop до тех пор, пока не будет выполнено while
l oop.
Во втором варианте новая функция обратного вызова исполнителя Promise также вызывается синхронно, поэтому снова блокирует событие l oop немедленно, пока не будет выполнено while
l oop.
В третьем варианте он вызывает:
Promise.resolve().then(yourCallback);
Promise.resolve()
создает уже выполненное обещание и затем вызывает .then(yourCallback)
для этого нового обещания. Это планирует запуск yourCallback
на следующем тике события l oop. Согласно спецификации обещания, обработчики .then()
всегда запускаются в будущем тике события l oop, даже если обещание уже выполнено.
Между тем, любые другие Javascript сразу после этого продолжают run, и только после этого Javascript интерпретатор переходит к следующему тику события l oop и запускает yourCallback
. Но когда он запускает этот обратный вызов, он выполняется в основном потоке и поэтому блокируется, пока не будет выполнен.
Создание и выполнение обещания все еще привязано к основному потоку. Только выполнение с разрешением Promise перемещается как микрозадача.
Весь ваш код в вашем примере выполняется в основном потоке. Обработчик .then()
запланирован для запуска в будущем тике события l oop (все еще в основном потоке). В этом планировании используется очередь микрозадач, которая позволяет ему ставить перед некоторыми другими вещами в очереди событий, но оно по-прежнему выполняется в основном потоке и по-прежнему выполняется в будущем тике события l oop.
Кроме того, фраза «выполнение обещания» немного неверна. Обещания - это система уведомлений, и вы планируете запускать обратные вызовы с ними в какой-то момент в будущем, используя .then()
, .catch()
или .finally()
в обещании. Итак, в общем, вы не хотите думать о «выполнении обещания». Ваш код выполняется, вызывая создание обещания, а затем вы регистрируете обратные вызовы для этого обещания для запуска в будущем в зависимости от того, что происходит с этим обещанием. Обещания - это специализированная система уведомлений о событиях.
Обещания помогают уведомить вас, когда что-то завершено, или помочь вам запланировать время выполнения. Они не перемещают задачи в другой поток.
В качестве иллюстрации вы можете вставить setTimeout(fn, 1)
сразу после третьего варианта и увидеть, что выполнение тайм-аута заблокировано до завершения третьего варианта. Вот пример этого. И я сделал все блокирующие петли длиной 1000 мс, чтобы вам было легче видеть. Запустите это в браузере здесь или скопируйте в файл node.js и запустите его там, чтобы увидеть, как setTimeout()
блокируется от своевременного выполнения на время выполнения longRunningTaskPromResolve()
. Итак, longRunningTaskPromResolve()
все еще блокируется. Помещение его в обработчик .then()
изменяется, когда он запускается, но он все еще блокируется.
const loopTime = 1000;
let startTime;
function log(...args) {
if (!startTime) {
startTime = Date.now();
}
let delta = (Date.now() - startTime) / 1000;
args.unshift(delta.toFixed(3) + ":");
console.log(...args);
}
function longRunningTask(){
log('longRunningTask() starting');
let start = Date.now();
while (Date.now() - start < loopTime) {}
log('** longRunningTask() done **');
}
function longRunningTaskProm(){
log('longRunningTaskProm() starting');
return new Promise((resolve, reject) => {
let start = Date.now();
while (Date.now() - start < loopTime) {}
log('About to call resolve() in longRunningTaskProm()');
resolve('** longRunningTaskProm().then(handler) called **');
});
}
function longRunningTaskPromResolve(){
log('longRunningTaskPromResolve() starting');
return Promise.resolve().then(v => {
log('Start running .then() handler in longRunningTaskPromResolve()');
let start = Date.now();
while (Date.now() - start < loopTime) {}
log('About to return from .then() in longRunningTaskPromResolve()');
return '** longRunningTaskPromResolve().then(handler) called **';
})
}
log('*** STARTING ***');
longRunningTask();
longRunningTaskProm().then(log);
longRunningTaskPromResolve().then(log);
log('Scheduling 1ms setTimeout')
setTimeout(() => {
log('1ms setTimeout Got to Run');
}, 1);
log('*** First sequence of code completed, returning to event loop ***');
Если вы запустите этот фрагмент и посмотрите, когда именно выводится каждое сообщение и время, связанное с каждым сообщением, вы можете увидеть точную последовательность того, когда все начинает работать.
Вот результат, когда я запустил его в node.js (номера строк добавлены, чтобы помочь с объяснением ниже):
1 0.000: *** STARTING ***
2 0.005: longRunningTask() starting
3 1.006: ** longRunningTask() done **
4 1.006: longRunningTaskProm() starting
5 2.007: About to call resolve() in longRunningTaskProm()
6 2.007: longRunningTaskPromResolve() starting
7 2.008: Scheduling 1ms setTimeout
8 2.009: *** First sequence of code completed, returning to event loop ***
9 2.010: ** longRunningTaskProm().then(handler) called **
10 2.010: Start running .then() handler in longRunningTaskPromResolve()
11 3.010: About to return from .then() in longRunningTaskPromResolve()
12 3.010: ** longRunningTaskPromResolve().then(handler) called **
13 3.012: 1ms setTimeout Got to Run
Вот пошаговая аннотация:
- Все начинается.
longRunningTask()
инициировано. longRunningTask()
завершено. Это полностью синхронно. longRunningTaskProm()
инициировано. longRunningTaskProm()
звонков resolve()
. Из этого видно, что функция исполнителя обещания (обратный вызов, переданный в new Promise (fn) `, также полностью синхронен. longRunningTaskPromResolve()
инициировано. Вы можете видеть, что обработчик из longRunningTaskProm().then(handler)
еще не Это было запланировано для выполнения на следующем тике события l oop, но, поскольку мы еще не вернулись к событию l oop, оно еще не было вызвано. - Сейчас мы устанавливаем таймер 1 мс. Обратите внимание, что этот таймер устанавливается только через 1 мс после того, как мы запустили
longRunningTaskPromResolve()
. Это потому, что longRunningTaskPromResolve()
пока мало что сделал. Он запустил Promise.resolve().then(handler)
, но все, что было сделано, было запланируйте запуск handler
в будущем тике события l oop. Итак, это заняло всего 1 мс, чтобы запланировать это. Долговременная часть этой функции еще не запущена. - Мы перейти к концу этой последовательности кода и вернуться к событию l oop.
- Следующее, что запланировано для запуска в событии l oop, - это обработчик из
longRunningTaskProm().then(handler)
, чтобы он был вызван . Видно, что он уже ждал, чтобы запустить sin ce он проработал всего 1 мс после того, как мы вернулись к событию l oop. Этот обработчик запускается, и мы возвращаемся к событию l oop. - Следующее, что запланировано для запуска в событии l oop, - это обработчик из
Promise.resolve().then(handler)
, поэтому теперь мы видим, что он начинает выполняться. и поскольку он уже был поставлен в очередь, он запускается сразу после завершения предыдущего события. - Для запуска l oop in
longRunningTaskPromResolve()
требуется ровно 1000 мсек, а затем он возвращается из своего .then()
обработчика, который планирует затем следующий обработчик .then()
в этой цепочке обещаний для запуска на следующем тике события l oop. - Этот
.then()
запускается. - Затем, наконец, когда есть нет запланированных обработчиков
.then()
, запускается обратный вызов setTimeout()
. Он был настроен на выполнение за 1 мс, но все действия обещания, выполнявшиеся с более высоким приоритетом перед ним, были отложены, поэтому вместо выполнения 1 мс он выполнялся за 1004 мс.