Синхронний та асинхронний callback
Синхронний callback виконується одразу під час виклику основної функції, у тому самому стеку викликів, а асинхронний виконується пізніше, вже після завершення поточного коду, через event loop. Різниця не в синтаксисі колбека, а в тому, коли саме функція, що його отримала, вирішує його викликати.
Теорія
TL;DR
- Синхронний callback виконується негайно, до завершення поточної функції, у тому самому call stack.
- Асинхронний callback ставиться в чергу і виконується після того, як стек звільниться.
- Приклади синхронних:
array.map(fn),forEach,sort,JSON.parseз reviver. - Приклади асинхронних:
setTimeout(fn, 0),fetch().then(fn), обробники подій. setTimeout(fn, 0)не означає «зараз», він означає «якнайшвидше, але після всього синхронного коду».
Швидкий приклад
function process(callback) {
console.log('Before the callback');
callback(); // runs immediately
console.log('After the callback');
}
process(() => console.log('Synchronous callback'));Вивід:
Before the callback
Synchronous callback
After the callbackУсе виконується по порядку, без затримок.
Синхронний callback
Синхронний callback виконується одразу, під час виклику основної функції, тобто в тому самому стеку викликів, ще до того як поточна функція завершиться. Він блокує подальше виконання: наступний рядок не почнеться, доки колбек не відпрацює.
const numbers = [1, 2, 3];
const doubled = numbers.map(n => {
console.log('processing', n); // runs on every element, right now
return n * 2;
});
console.log(doubled); // [2, 4, 6] is already available on this lineСаме тому результат map доступний одразу після виклику: колбек не «планується», він просто викликається.
Асинхронний callback
Асинхронний callback викликається пізніше, після завершення поточного коду: за таймером, після запиту, після події. Він потрапляє в чергу задач, а event loop візьме його лише тоді, коли call stack буде порожній.
function processAsync(callback) {
console.log('Before the callback');
setTimeout(callback, 0); // will run later
console.log('After the callback');
}
processAsync(() => console.log('Asynchronous callback'));Вивід:
Before the callback
After the callback
Asynchronous callbackКолбек виконується не одразу, а після того, як стек викликів звільниться, навіть якщо затримка дорівнює нулю.
Порівняння
| Тип | Коли виконується | Приклад |
|---|---|---|
| Синхронний | Одразу, під час виклику | array.map(fn) |
| Асинхронний | Пізніше, після основного коду | setTimeout(fn, 0), fetch().then(fn) |
Черга мікрозадач і макрозадач
Серед асинхронних колбеків теж є порядок. Колбеки Promise потрапляють у чергу мікрозадач, яку event loop спустошує одразу після поточного синхронного коду, а setTimeout дає макрозадачу і чекає наступної ітерації циклу.
console.log('1 sync');
setTimeout(() => console.log('4 macrotask'), 0);
Promise.resolve().then(() => console.log('3 microtask'));
console.log('2 sync');
// order: 1 sync, 2 sync, 3 microtask, 4 macrotaskТипові помилки
- Читати результат асинхронної операції одразу після виклику функції: там ще
undefined, бо колбек не встиг виконатися. - Вважати, що
setTimeout(fn, 0)виконає функцію негайно: вона чекає на порожній call stack. - Ловити помилку з асинхронного колбека зовнішнім
try/catch: коли колбек виконується, той блок уже завершився. - Ставити
returnу колбекуforEachі сподіватися, що це поверне значення із зовнішньої функції:forEachігнорує результат. - Блокувати потік важким синхронним колбеком у циклі: сторінка перестає реагувати, бо стек не звільняється.
- Плутати чергу мікрозадач і макрозадач:
Promise.thenзавжди випередитьsetTimeout(..., 0).
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.