Асинхронный JavaScript
Колбэки, промисы и async/await в Node.js
- объяснить цикл событий и почему Node не блокируется,
- читать и писать код с колбэками (и распознавать «ад колбэков»),
- использовать промисы с
.then()/.catch(), - писать понятный асинхронный код с
async/await.
1. Блокирующий и неблокирующий код
Представь повара, который принимает заказ, а потом застывает у духовки на 20 минут, пока блюдо не готово, и только тогда обслуживает остальных. Это блокировка — пока идёт одна медленная задача, больше ничего не происходит.
Node.js работает как умный официант: он ставит блюдо в духовку, заводит таймер и продолжает обслуживать другие столики. Когда таймер зазвонит, он приносит блюдо. Это неблокирующая работа. Посмотри на порядок сообщений ниже:
const fs = require('fs');
// Node does NOT wait here - it starts the read and moves on
fs.readFile('note.txt', 'utf8', function (err, data) {
if (err) throw err;
console.log('Inside callback:', data);
});
console.log('After readFile call');
// Output:
// After readFile call
// Inside callback: Hello from the file!
Строка After readFile call печатается первой, хотя написана последней, потому что Node не стал ждать файл. Когда файл загрузился, Node выполняет функцию-колбэк.
2. Цикл событий (event loop)
Как один поток делает много дел «одновременно»? Секрет — цикл событий. Твой синхронный код выполняется на стеке вызовов. Медленные задачи (таймеры, чтение файлов, сеть) передаются фоновым API Node. Когда задача завершается, её колбэк ждёт в очереди. Цикл событий постоянно проверяет: «стек пуст? тогда возьми следующий колбэк из очереди и выполни его».
Очередей две. Микрозадачи (разрешённые промисы) обрабатываются раньше следующей макрозадачи (например, setTimeout). Этот порядок объясняет классическую загадку:
console.log('1: start');
setTimeout(function () {
console.log('2: timeout');
}, 0);
Promise.resolve().then(function () {
console.log('3: promise');
});
console.log('4: end');
// Output:
// 1: start
// 4: end
// 3: promise
// 2: timeout
Сначала выполняются обе синхронные строки (1, 4). Затем колбэк промиса (3), потому что микрозадачи идут первыми. Колбэк таймера (2) — последним.