Пошук уроків, статей та іншого контенту
Застосуєте callback-функції для обробки результатів асинхронних операцій і визначите їхні основні обмеження.
Callback-функція — це функція, яку передають в іншу функцію як аргумент, щоб викликати пізніше.
Простий приклад:
function greet(name, callback) {
const message = `Привіт, ${name}!`;
callback(message);
}
function printMessage(message) {
console.log(message);
}
greet("Олено", printMessage);Функція printMessage передається у greet як callback. Усередині greet вона викликається після формування повідомлення.
Callback не обов’язково пов’язаний з асинхронністю. Його можна використовувати і в синхронному коді:
const numbers = [1, 2, 3];
numbers.forEach(function (number) {
console.log(number);
});У цьому прикладі функція передається в forEach і викликається для кожного елемента масиву.
Асинхронна операція може завершитися не одразу. Наприклад:
таймер;
запит до сервера;
читання файлу;
отримання даних із бази даних.
JavaScript не зупиняє виконання всієї програми, поки така операція триває. Натомість можна передати callback, який виконається після завершення операції.
Для імітації асинхронної операції використаємо setTimeout:
function loadUser(callback) {
setTimeout(function () {
const user = {
id: 1,
name: "Олена"
};
callback(user);
}, 1000);
}
console.log("Початок");
loadUser(function (user) {
console.log("Користувач завантажений:", user);
});
console.log("Кінець");Результат:
Початок
Кінець
Користувач завантажений: { id: 1, name: 'Олена' }Callback виконується через одну секунду, тому повідомлення "Кінець" з’являється раніше.
Викликається console.log("Початок").
Запускається loadUser.
setTimeout планує виконання функції через одну секунду.
JavaScript продовжує виконання програми.
Виводиться "Кінець".
Через одну секунду створюється об’єкт користувача.
Викликається callback із цим об’єктом.
Асинхронна функція зазвичай передає результат у callback:
function calculateSum(firstNumber, secondNumber, callback) {
setTimeout(function () {
const result = firstNumber + secondNumber;
callback(result);
}, 500);
}
calculateSum(10, 5, function (sum) {
console.log("Сума:", sum);
});Callback отримує значення sum після завершення обчислення.
Такий підхід дає змогу використати результат саме тоді, коли він стане доступним.
Асинхронна операція може завершитися успішно або з помилкою. Один із поширених підходів — передавати в callback спочатку помилку, а потім результат.
Цей підхід часто називають error-first callback:
function getUser(userId, callback) {
setTimeout(function () {
if (userId <= 0) {
callback(new Error("Некоректний ідентифікатор користувача"));
return;
}
const user = {
id: userId,
name: "Олена"
};
callback(null, user);
}, 500);
}
getUser(1, function (error, user) {
if (error) {
console.error("Помилка:", error.message);
return;
}
console.log("Користувач:", user);
});У цьому прикладі:
error містить об’єкт помилки або null;
user містить результат або undefined;
спочатку потрібно перевірити помилку;
якщо помилки немає, можна працювати з результатом.
Виклик із помилкою:
getUser(0, function (error, user) {
if (error) {
console.error("Помилка:", error.message);
return;
}
console.log(user);
});Функція завершується через return, щоб код обробки успішного результату не виконався після помилки.
Іноді результат однієї операції потрібен для запуску іншої. Наприклад, спочатку потрібно завантажити користувача, а потім його замовлення.
function getUser(callback) {
setTimeout(function () {
callback(null, {
id: 1,
name: "Олена"
});
}, 500);
}
function getOrders(userId, callback) {
setTimeout(function () {
callback(null, [
{ id: 101, total: 250 },
{ id: 102, total: 480 }
]);
}, 500);
}
getUser(function (error, user) {
if (error) {
console.error(error.message);
return;
}
getOrders(user.id, function (error, orders) {
if (error) {
console.error(error.message);
return;
}
console.log("Користувач:", user.name);
console.log("Замовлення:", orders);
});
});getOrders можна викликати лише після отримання user, тому друга операція розміщена всередині callback першої.
Якщо асинхронних операцій стає багато, callback-функції починають вкладатися одна в одну. Такий код називають callback hell або «пеклом callback-функцій».
getUser(function (error, user) {
if (error) {
console.error(error);
return;
}
getOrders(user.id, function (error, orders) {
if (error) {
console.error(error);
return;
}
getOrderDetails(orders[0].id, function (error, details) {
if (error) {
console.error(error);
return;
}
saveDetails(details, function (error) {
if (error) {
console.error(error);
return;
}
console.log("Дані збережено");
});
});
});
});Основні проблеми такого коду:
складно стежити за вкладеністю;
важче читати логіку програми;
помилки потрібно обробляти на кожному рівні;
складніше змінювати й тестувати код.
Для сучасного JavaScript частіше використовують Promise та async/await. Вони допомагають записувати послідовні асинхронні операції у зрозумілішій формі.
Водночас callback-функції залишаються важливою основою асинхронного програмування. Promise та async/await також використовують функції, які викликаються після завершення операцій.
Callback можна передати як анонімну функцію:
setTimeout(function () {
console.log("Час минув");
}, 1000);Або заздалегідь створити іменовану функцію:
function showMessage() {
console.log("Час минув");
}
setTimeout(showMessage, 1000);Важливо передавати саму функцію, а не результат її виклику:
setTimeout(showMessage, 1000);Неправильний варіант:
setTimeout(showMessage(), 1000);У неправильному варіанті showMessage() викликається одразу, а результат цього виклику передається в setTimeout.
Для коротких callback-функцій часто використовують стрілковий синтаксис:
function loadData(callback) {
setTimeout(() => {
callback("Дані отримано");
}, 500);
}
loadData((message) => {
console.log(message);
});Якщо callback має один параметр, дужки навколо нього можна не писати:
loadData(message => {
console.log(message);
});Обидва варіанти працюють однаково.
function loadUser(callback) {
let user;
setTimeout(() => {
user = { name: "Олена" };
}, 500);
callback(user);
}Тут callback викликається одразу, ще до того, як setTimeout створить користувача.
Правильно:
function loadUser(callback) {
setTimeout(() => {
const user = { name: "Олена" };
callback(user);
}, 500);
}function loadMessage(callback) {
setTimeout(() => {
const message = "Готово";
// Callback не викликається
}, 500);
}Якщо callback не викликати, код, який очікує на результат, не виконається.
function checkValue(value, callback) {
if (value > 0) {
callback(null, "Додатне число");
}
callback(new Error("Значення має бути нульовим або від’ємним"));
}Для додатного значення callback буде викликано двічі. Після успішного виклику потрібно завершити виконання:
function checkValue(value, callback) {
if (value > 0) {
callback(null, "Додатне число");
return;
}
callback(new Error("Значення має бути нульовим або від’ємним"));
}getUser(1, function (error, user) {
console.log(user.name);
});Якщо операція завершиться помилкою, user може не існувати. Спочатку завжди перевіряйте error:
getUser(1, function (error, user) {
if (error) {
console.error(error.message);
return;
}
console.log(user.name);
});Callback-функція — це функція, передана іншій функції для подальшого виклику.
В асинхронному коді callback дає змогу:
отримати результат після завершення операції;
обробити успішний результат;
повідомити про помилку;
виконати кілька операцій послідовно.
Основні обмеження callback-підходу:
вкладені callback-функції погіршують читабельність;
помилки потрібно обробляти вручну;
легко випадково викликати callback занадто рано або кілька разів;
складні послідовності призводять до callback hell.
Callback — це функція, передана як аргумент іншій функції.
Асинхронний callback викликається після завершення операції.
Результат операції можна передати аргументом callback.
Для помилок часто використовують формат callback(error, result).
Перед callback не потрібно ставити дужки, якщо ви передаєте саму функцію.
Вкладені callback-функції можуть зробити код складним для читання.
Для складнішої асинхронної логіки зручно використовувати Promise та async/await.