Node.js визуализация: promise, async/await and process.nextTick
В статье предполагается, что вы знакомы с event loop и его фазами. Promise спасли положение тысячам разработчиков JavaScript, но знаем ли мы, как они работают под капотом?
В свое время ад обратного вызова был нормой для обработки асинхронных операций. Простой JavaScript-скрипт для чтения файла и вставки его содержимого в базу данных мог бы быть похож на этот фрагмент.
Нам больше не нужно иметь дело с этой вложенной структурой из-за promise и async
/await
.
Демистификация promise
Определение MDN для promise - это «возможное завершение (или сбой) асинхронной операции».
Определение выглядит так, как будто мы имеем дело с какой-то формой непонятной магии, но, проще говоря, promise
- это контейнер для будущей ценности.
promise
ведет себя в определенной степени как браузер, содержащий либо желаемое содержимое страницы, либо сообщение об ошибке. Всякий раз, когда мы переходим по адресу, ваш веб-браузер может находиться в трех разных состояниях:
- При извлечении данных может быть показана пустая страница или загрузочный счетчик.
- Содержимое страницы успешно отображено.
- Может появиться ошибка.
Promise может иметь три статуса:
pending
, когда, например, запрос не завершен;fulfilled
, когда сетевой запрос будет завершен;rejected
, когда асинхронный запрос завершится.
Практический пример статуса promise
Недавно созданное promise находится в состоянии pending
.
Аналогично, операция ввода-вывода (I/O), такая как запрос к базе данных, находится в pending
до тех пор, пока она не разрешится или не выдаст ошибку.
Мы можем прикрепить прослушиватель .then
/.catch
для обработки будущего значения promise
. Как только вызывается функция resolve
/reject
, promise меняет свой статус с pending
на fulfilled
/rejected
.
async/await
Современный JavaScript полностью основан на async
/await
, но эти два выражения являются альтернативным способом написания promise.
Мы можем переписать фрагмент базы данных с promise на async
/await
или наоборот.
Мы можем перепутать синтаксис и сбить с толку наших коллег (крайне не рекомендуется для удобства чтения и согласованности).
Promise под капотом
Promise, по-видимому, обрабатываются синхронно, но их выполнение откладывается до определенного момента в будущем.
Следующий сценарий показывает эту console.log
запускаются перед обратным вызовом promise.
Под капотом двигатель V8 запоминает функцию обратного вызова и помещает ее в очередь микрозадач, когда promise resolve
/reject
.
Как только стек вызовов опустеет, V8 сможет обрабатывать микрозадачи.
Очередь микрозадач Promise и цикл событий
Очередь микрозадач и цикл событий ожидают, пока стек вызовов опустеет, прежде чем отправлять задачи на выполнение, но какая из них имеет более высокий приоритет?
Следующий сценарий показывает, что Node.js и его внутренние компоненты всегда будут пытаться исчерпать очередь микрозадач, прежде чем передавать управление обратно циклу событий и его фазам:
setTimeout
и Promise.reject
ставят в очередь макрозадачу на этапе таймера и микрозадачу.
Обратный вызов в очереди микрозадач выполняется перед фазой таймера цикла событий. Как только микрозадач не останется, цикл событий может возобновить свое выполнение.
Обратный вызов setTimeout
вызывает setImmediate
и запускает другую микрозадачу promise
, которая будет выполнена в пустом стеке вызовов.
process.nextTick
process.nextTick
также планирует микрозадачи, которые Node.js управляется в выделенной очереди и имеет более высокий приоритет (в CommonJS), чем очередь promise и фазы цикла событий.
В следующем сценарии, Node.js помещает обратный вызов в их очередь.
Как только стек вызовов опустеет, Node.js обрабатывает микрозадачи process.nextTick
, за которыми следуют очередь promise
и фазы цикла событий.