Пошук уроків, статей та іншого контенту
Навчимося створювати callback-операції, передавати результати й обробляти помилки в callback-стилі.
Callback — це функція, яку передають іншій функції як аргумент, щоб викликати пізніше.
Callback використовують, коли операція завершується не одразу. Наприклад:
читання файлу;
запит до сервера;
таймер;
обробка даних.
Замість того щоб повертати результат одразу, функція викликає callback після завершення роботи.
function greet(name, callback) {
const message = `Привіт, ${name}!`;
callback(message);
}
greet("Олено", function (message) {
console.log(message);
});Результат:
Привіт, Олено!У цьому прикладі:
greet отримує ім’я та callback.
Функція створює повідомлення.
callback(message) передає повідомлення в callback.
Передана функція виводить повідомлення в консоль.
Callback можна записати як звичайну функцію або як стрілкову функцію:
function processValue(value, callback) {
callback(value * 2);
}
processValue(5, (result) => {
console.log(result);
});Результат:
10У Node.js багато операцій є асинхронними. Це означає, що Node.js запускає операцію, але не зупиняє виконання всієї програми, очікуючи на її завершення.
Для демонстрації використаємо setTimeout:
function getMessage(callback) {
setTimeout(() => {
callback("Повідомлення отримано");
}, 1000);
}
console.log("Початок");
getMessage((message) => {
console.log(message);
});
console.log("Кінець");Результат:
Початок
Кінець
Повідомлення отриманоCallback виконується приблизно через одну секунду. Тому рядок Кінець виводиться раніше, ніж повідомлення з callback.
Схематично виконання виглядає так:
Виводиться Початок.
Запускається асинхронний таймер.
Виводиться Кінець.
Після завершення таймера викликається callback.
Виводиться Повідомлення отримано.
Callback може отримувати один або кілька аргументів. Наприклад, функція може виконати обчислення й передати результат:
function calculateSum(firstNumber, secondNumber, callback) {
setTimeout(() => {
const sum = firstNumber + secondNumber;
callback(sum);
}, 500);
}
calculateSum(7, 3, (result) => {
console.log(`Сума: ${result}`);
});Результат:
Сума: 10Функція calculateSum не повертає суму через return. Вона передає результат у callback:
callback(sum);Це важливо для асинхронних операцій. Якщо використати return усередині асинхронної функції, результат не буде доступний у місці виклику так, як очікується:
function calculateSum(firstNumber, secondNumber) {
setTimeout(() => {
return firstNumber + secondNumber;
}, 500);
}
const result = calculateSum(7, 3);
console.log(result);Результат:
undefinedreturn повертає значення з функції, яка виконується всередині setTimeout, а не з calculateSum. Для передавання асинхронного результату потрібно використати callback.
У Node.js поширений шаблон для callback має назву error-first callback.
У цьому стилі callback отримує два аргументи:
callback(error, result);Правила такі:
перший аргумент містить помилку або null;
другий аргумент містить результат успішної операції;
спочатку перевіряють помилку;
результат використовують лише якщо помилки немає.
Приклад успішної операції:
function divide(firstNumber, secondNumber, callback) {
if (secondNumber === 0) {
callback(new Error("Ділення на нуль неможливе"));
return;
}
const result = firstNumber / secondNumber;
callback(null, result);
}
divide(10, 2, (error, result) => {
if (error) {
console.error(error.message);
return;
}
console.log(`Результат: ${result}`);
});Результат:
Результат: 5Першим аргументом передано null, тому що помилки немає. Другим аргументом передано результат 5.
Тепер викличемо функцію з помилковими даними:
function divide(firstNumber, secondNumber, callback) {
if (secondNumber === 0) {
callback(new Error("Ділення на нуль неможливе"));
return;
}
callback(null, firstNumber / secondNumber);
}
divide(10, 0, (error, result) => {
if (error) {
console.error(`Помилка: ${error.message}`);
return;
}
console.log(`Результат: ${result}`);
});Результат:
Помилка: Ділення на нуль неможливеВиклик return після callback потрібен, щоб функція не продовжила виконання. Без нього після помилки може бути викликаний і код успішного результату.
У реальних програмах результат і помилка часто передаються після асинхронної операції.
function getUser(userId, callback) {
setTimeout(() => {
if (userId <= 0) {
callback(new Error("Ідентифікатор користувача має бути додатним"));
return;
}
const user = {
id: userId,
name: "Марія"
};
callback(null, user);
}, 500);
}
getUser(1, (error, user) => {
if (error) {
console.error(`Помилка: ${error.message}`);
return;
}
console.log(`Користувач: ${user.name}`);
});Результат:
Користувач: МаріяУспішний сценарій:
callback(null, user);Сценарій із помилкою:
callback(new Error("Ідентифікатор користувача має бути додатним"));Перевірка callback має починатися з помилки:
if (error) {
console.error(error.message);
return;
}Це запобігає використанню результату, якого немає або який може бути некоректним.
Іноді результат однієї асинхронної операції потрібен для наступної.
function getUser(userId, callback) {
setTimeout(() => {
if (userId <= 0) {
callback(new Error("Некоректний ідентифікатор користувача"));
return;
}
callback(null, {
id: userId,
name: "Марія"
});
}, 300);
}
function getGreeting(user, callback) {
setTimeout(() => {
callback(null, `Привіт, ${user.name}!`);
}, 300);
}
getUser(1, (error, user) => {
if (error) {
console.error(error.message);
return;
}
getGreeting(user, (error, greeting) => {
if (error) {
console.error(error.message);
return;
}
console.log(greeting);
});
});Результат:
Привіт, Марія!Друга операція запускається всередині callback першої, тому що їй потрібен об’єкт user.
Якщо послідовних операцій багато, callback-и починають вкладатися один в один:
firstOperation((error, firstResult) => {
if (error) {
return;
}
secondOperation(firstResult, (error, secondResult) => {
if (error) {
return;
}
thirdOperation(secondResult, (error, thirdResult) => {
if (error) {
return;
}
console.log(thirdResult);
});
});
});Таку структуру називають callback hell — «пекло callback-ів».
Callback-и самі по собі не є проблемою. Проблема виникає, коли:
операцій стає багато;
код має кілька рівнів вкладеності;
перевірки помилок повторюються;
логіку стає складно читати й тестувати.
У цьому уроці важливо розуміти сам принцип callback-ів: функцію можна передати іншій функції, викликати після завершення операції та передати через неї результат або помилку.
Під час створення callback-операцій дотримуйтеся таких правил:
Називайте аргумент помилки error.
Використовуйте перший аргумент для помилки, а другий — для результату.
Передавайте null, якщо помилки немає.
Перевіряйте помилку перед використанням результату.
Після обробки помилки завершуйте виконання callback-а через return.
Викликайте callback один раз для кожного завершення операції.
Викликайте callback після завершення асинхронної роботи, а не до неї.
Загальний шаблон:
function doSomething(input, callback) {
setTimeout(() => {
if (!input) {
callback(new Error("Вхідні дані відсутні"));
return;
}
const result = `Оброблено: ${input}`;
callback(null, result);
}, 300);
}
doSomething("дані", (error, result) => {
if (error) {
console.error(error.message);
return;
}
console.log(result);
});Неправильно:
setTimeout(console.log("Готово"), 1000);console.log("Готово") викликається одразу, а setTimeout отримує його результат.
Правильно:
setTimeout(() => {
console.log("Готово");
}, 1000);Або:
setTimeout(function () {
console.log("Готово");
}, 1000);Небезпечно використовувати результат без перевірки:
getUser(1, (error, user) => {
console.log(user.name);
});Якщо операція завершиться помилкою, user може бути відсутнім.
Правильно:
getUser(1, (error, user) => {
if (error) {
console.error(error.message);
return;
}
console.log(user.name);
});Для error-first callback дотримуйтеся порядку:
callback(error, result);Правильно:
callback(null, result);Неправильно:
callback(result, null);Неправильно:
function divide(firstNumber, secondNumber, callback) {
if (secondNumber === 0) {
callback(new Error("Ділення на нуль неможливе"));
}
callback(null, firstNumber / secondNumber);
}Якщо дільник дорівнює нулю, callback буде викликаний двічі.
Правильно:
function divide(firstNumber, secondNumber, callback) {
if (secondNumber === 0) {
callback(new Error("Ділення на нуль неможливе"));
return;
}
callback(null, firstNumber / secondNumber);
}Callback — це функція, передана іншій функції як аргумент.
Callback можна викликати після завершення синхронної або асинхронної операції.
Результат передають у callback через його аргументи.
У Node.js часто використовують шаблон callback(error, result).
Якщо помилки немає, першим аргументом передають null.
Помилку потрібно перевіряти перед використанням результату.
Після обробки помилки варто завершити виконання через return.
Велика кількість вкладених callback-ів ускладнює читання коду.