Пошук уроків, статей та іншого контенту
Розберете різницю між синхронним і асинхронним виконанням та зрозумієте, навіщо JavaScript працює з відкладеними операціями.
Синхронний код виконується послідовно: JavaScript переходить до наступної інструкції лише після завершення поточної.
console.log("Перша операція");
console.log("Друга операція");
console.log("Третя операція");Результат:
Перша операція
Друга операція
Третя операціяКожна команда виконується в тому самому порядку, у якому записана.
Якщо синхронна операція займає багато часу, вона блокує виконання всього іншого коду.
Наприклад, цей цикл навмисно витрачає час:
console.log("Початок");
const start = Date.now();
while (Date.now() - start < 2000) {
// Очікуємо приблизно дві секунди
}
console.log("Кінець");Поки виконується цикл while, JavaScript не може перейти до інших команд. У браузері через це може перестати реагувати сторінка.
Асинхронний код дозволяє запустити операцію, яка завершиться пізніше, і не блокувати виконання інших команд.
Типові приклади асинхронних операцій:
таймери;
запити до сервера;
читання файлів;
робота з базою даних;
очікування дії користувача.
Наприклад, setTimeout запускає функцію після вказаної затримки:
console.log("Початок");
setTimeout(() => {
console.log("Повідомлення з таймера");
}, 1000);
console.log("Кінець");Результат:
Початок
Кінець
Повідомлення з таймераХоча виклик setTimeout записаний перед console.log("Кінець"), його функція виконається пізніше. JavaScript не зупиняється в очікуванні таймера.
Затримка
1000мілісекунд означає, що функція може бути виконана не раніше ніж через одну секунду. Це не гарантує виконання рівно через одну секунду.
Уявімо вебсторінку, яка надсилає запит до сервера. Відповідь може прийти через кілька секунд.
Якби JavaScript повністю зупинявся під час очікування, користувач не зміг би:
натискати кнопки;
прокручувати сторінку;
вводити текст;
взаємодіяти з іншими елементами.
Асинхронний підхід дозволяє виконувати інші завдання, поки програма очікує результат повільної операції.
JavaScript виконує звичайний код в одному основному потоці. Це означає, що в один момент часу виконується одна інструкція.
Асинхронні операції обробляються середовищем виконання:
браузером;
Node.js;
іншим середовищем, у якому працює JavaScript.
Коли асинхронна операція завершується, її функція не виконується миттєво посеред поточної команди. Вона потрапляє в чергу завдань. JavaScript виконає її, коли завершить поточний синхронний код.
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
console.log("3");Результат:
1
3
2Навіть із затримкою 0 функція таймера виконується пізніше, після завершення синхронного коду.
Колбек — це функція, яку передають в іншу функцію, щоб викликати пізніше.
function showMessage(message, callback) {
setTimeout(() => {
callback(message);
}, 1000);
}
showMessage("Операція завершена", (message) => {
console.log(message);
});У цьому прикладі:
showMessage отримує текст і функцію callback.
Через одну секунду викликається callback.
Повідомлення виводиться в консоль.
Колбеки широко використовувалися для асинхронного коду, але велика кількість вкладених колбеків може ускладнити читання програми.
Promise — це об’єкт, який представляє майбутній результат асинхронної операції.
Promise може перебувати в одному з трьох станів:
pending — очікування;
fulfilled — операція успішно завершена;
rejected — операція завершилася помилкою.
Створимо власний Promise:
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Очікування завершено");
}, milliseconds);
});
}
wait(1000).then((message) => {
console.log(message);
});Метод .then() виконується після успішного завершення Promise.
Для обробки помилки використовують .catch():
function getUser() {
return Promise.reject(new Error("Не вдалося отримати користувача"));
}
getUser()
.then((user) => {
console.log(user);
})
.catch((error) => {
console.error(error.message);
});async і awaitКлючові слова async та await допомагають писати асинхронний код у послідовнішому стилі.
Функція з ключовим словом async завжди повертає Promise:
async function getMessage() {
return "Привіт";
}
getMessage().then((message) => {
console.log(message);
});await призупиняє виконання лише поточної async-функції до завершення Promise. Він не блокує весь JavaScript.
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Дані отримано");
}, milliseconds);
});
}
async function loadData() {
console.log("Початок завантаження");
const message = await wait(1000);
console.log(message);
console.log("Завантаження завершено");
}
loadData();
console.log("Цей код виконується, не чекаючи таймера");Можливий результат:
Початок завантаження
Цей код виконується, не чекаючи таймера
Дані отримано
Завантаження завершеноtry...catchАсинхронні помилки зручно обробляти за допомогою try...catch:
function loadUser() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Сервер недоступний"));
}, 500);
});
}
async function showUser() {
try {
const user = await loadUser();
console.log(user);
} catch (error) {
console.error("Помилка:", error.message);
}
}
showUser();Якщо Promise буде відхилено через reject, виконання перейде до блоку catch.
Іноді асинхронні операції потрібно виконувати одна за одною:
function wait(milliseconds, message) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(message);
}, milliseconds);
});
}
async function runSequentially() {
const first = await wait(1000, "Перша операція");
console.log(first);
const second = await wait(1000, "Друга операція");
console.log(second);
}
runSequentially();Загальний час виконання буде приблизно дві секунди.
Якщо операції не залежать одна від одної, їх можна запустити паралельно за допомогою Promise.all:
function wait(milliseconds, message) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(message);
}, milliseconds);
});
}
async function runInParallel() {
const firstTask = wait(1000, "Перша операція");
const secondTask = wait(1000, "Друга операція");
const results = await Promise.all([firstTask, secondTask]);
console.log(results);
}
runInParallel();Обидві операції починаються майже одночасно, тому загальний час буде приблизно одна секунда.
Promise.all завершується помилкою, якщо хоча б один із переданих Promise буде відхилено.
У реальних програмах синхронні та асинхронні операції використовуються разом:
console.log("1. Запуск програми");
setTimeout(() => {
console.log("4. Завершення асинхронної операції");
}, 1000);
console.log("2. Виконання синхронного коду");
console.log("3. Очікування завершення програми");Порядок буде таким:
Синхронний код виконується одразу.
Таймер реєструється для подальшого виконання.
Поточний синхронний код завершується.
Після завершення таймера виконується його функція.
setTimeout поверне результатНеправильно:
function getMessage() {
setTimeout(() => {
return "Готово";
}, 1000);
}
const message = getMessage();
console.log(message);Функція getMessage не повертає результат таймера. Вона завершиться раніше, тому message матиме значення undefined.
Потрібно використати Promise:
function getMessage() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Готово");
}, 1000);
});
}
async function showMessage() {
const message = await getMessage();
console.log(message);
}
showMessage();await поза async-функцієюЗазвичай await використовують усередині функції, оголошеної з async:
async function main() {
const result = await Promise.resolve("Готово");
console.log(result);
}
main();Асинхронна операція може завершитися невдало. Тому важливі операції варто обробляти через .catch() або try...catch.
async function main() {
try {
const result = await Promise.reject(new Error("Помилка"));
console.log(result);
} catch (error) {
console.error(error.message);
}
}
main();Якщо дві операції незалежні, не обов’язково чекати на кожну окремо. Використовуйте Promise.all, щоб виконати їх паралельно та скоротити час очікування.
Нижче програма імітує завантаження даних про користувача та його повідомлень:
function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "Олена" });
}, 1000);
});
}
function fetchMessages() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["Привіт", "Як справи?"]);
}, 1000);
});
}
async function showDashboard() {
try {
const [user, messages] = await Promise.all([
fetchUser(),
fetchMessages(),
]);
console.log(`Користувач: ${user.name}`);
console.log("Повідомлення:", messages);
} catch (error) {
console.error("Не вдалося завантажити дані:", error.message);
}
}
showDashboard();Запити до користувача та повідомлень запускаються паралельно. Це швидше, ніж спочатку чекати користувача, а потім окремо завантажувати повідомлення.
Синхронний код:
виконується послідовно;
очікує завершення кожної операції;
може блокувати виконання програми;
зручний для коротких простих обчислень.
Асинхронний код:
дозволяє не чекати повільні операції без потреби;
використовується для таймерів, запитів і введення-виведення;
працює через колбеки, Promise або async/await;
допомагає програмі залишатися доступною для користувача.
Синхронний код виконується крок за кроком і блокує наступні операції.
Асинхронний код дозволяє продовжити виконання, поки повільна операція ще триває.
setTimeout запускає функцію пізніше, навіть якщо затримка дорівнює нулю.
Promise описує майбутній результат асинхронної операції.
async та await роблять асинхронний код зрозумілішим.
Для обробки помилок використовують .catch() або try...catch.
Незалежні асинхронні операції можна виконувати паралельно за допомогою Promise.all.