Если вы как и я, привыкли к синхронному PHP, где всё выполняется по порядку, то асинхронность в JavaScript может показаться чем-то пугающим. Но не волнуйтесь, async/await — это как раз та фича, которая делает асинхронный код похожим на обычный, понятный и линейный. В этой статье мы разберемся, как это работает, на примерах, которые вы сможете применить уже сегодня.
Что такое async/await и зачем это нужно
Представьте, что вы заказываете пиццу. В синхронном мире вы стоите у телефона и ждете, пока оператор примет заказ, потом ждете курьера, потом едите. Всё последовательно. В асинхронном мире вы можете заниматься своими делами, пока пицца готовится. JavaScript изначально асинхронный: он не блокирует выполнение кода, когда ждет ответа от сервера или чтения файла.
Раньше для этого использовали колбэки (функции обратного вызова), но это приводило к «аду колбэков» — когда один колбэк вложен в другой, и код становится нечитаемым. Потом пришли промисы (объекты, представляющие результат асинхронной операции), но всё равно нужно было писать цепочки .then(). И вот async/await — это синтаксический сахар, который делает работу с промисами такой же простой, как с обычным кодом.
Как работает async и await: разбор на примере
Давайте начнем с простого. Допустим, у нас есть функция, которая имитирует запрос к серверу и возвращает данные через 2 секунды. Напишем её с помощью промиса:
function getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: 'Товар' });
}, 2000);
});
}Теперь вызовем её с async/await:
async function fetchData() {
console.log('Начинаем запрос...');
const data = await getData();
console.log('Данные получены:', data);
}
fetchData();Что здесь происходит? Ключевое слово await говорит JavaScript: «Подожди, пока промис не завершится, и только потом продолжай выполнение». При этом выполнение самой функции приостанавливается, но другие части программы (например, обработка кликов) работают дальше. Это не блокирует весь скрипт.
Обработка ошибок: try...catch в действии
В асинхронном коде ошибки могут возникать, как и в любом другом. С async/await мы используем старый добрый try...catch, который знаком каждому PHP-разработчику. Это гораздо удобнее, чем ловить ошибки через .catch() у промисов.
Пример с ошибкой:
function getDataWithError() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Сервер не отвечает'));
}, 1000);
});
}
async function fetchDataSafe() {
try {
const data = await getDataWithError();
console.log(data);
} catch (error) {
console.error('Ошибка:', error.message);
}
}
fetchDataSafe();Как видите, код остаётся линейным и читаемым. Если бы мы использовали промисы, пришлось бы писать цепочку .catch().
Параллельное выполнение: Promise.all и async/await
Иногда нужно запустить несколько асинхронных операций одновременно и дождаться их всех. Например, получить данные о пользователе и список его заказов. Если использовать await по очереди, то будет медленно. Решение — Promise.all.
Пример:
function getUser() {
return new Promise(resolve => setTimeout(() => resolve({ name: 'Иван' }), 1000));
}
function getOrders() {
return new Promise(resolve => setTimeout(() => resolve(['Заказ1', 'Заказ2']), 1500));
}
async function loadDashboard() {
try {
const [user, orders] = await Promise.all([getUser(), getOrders()]);
console.log('Пользователь:', user);
console.log('Заказы:', orders);
} catch (error) {
console.error('Ошибка загрузки:', error);
}
}
loadDashboard();Promise.all принимает массив промисов и возвращает один промис, который завершается, когда завершаются все переданные. Если хотя бы один промис завершится ошибкой, то весь Promise.all завершится с этой ошибкой.
| Метод | Описание | Когда использовать |
|---|---|---|
| Promise.all | Ждёт завершения всех промисов. Если один упадёт, то весь промис упадёт. | Когда все операции обязательны для успеха. |
| Promise.allSettled | Ждёт завершения всех промисов, неважно, с ошибкой или успехом. | Когда нужно дождаться всех, даже если некоторые упадут. |
| Promise.race | Ждёт первый завершённый промис (успех или ошибка). | Для таймаутов или когда нужен самый быстрый ответ. |
Асинхронные функции в циклах: подводные камни
Многие новички делают ошибку, используя await внутри обычного forEach. forEach не ждёт завершения асинхронной функции, поэтому код выполняется неправильно. Вместо этого используйте for...of или Promise.all с маппингом.
Пример неправильного кода:
const ids = [1, 2, 3];
ids.forEach(async (id) => {
const data = await fetchData(id);
console.log(data);
});
console.log('Цикл завершён'); // Выполнится раньше, чем данные придутПравильный вариант с for...of:
async function processIds(ids) {
for (const id of ids) {
const data = await fetchData(id);
console.log(data);
}
}
processIds([1, 2, 3]);Или, если порядок не важен, используйте Promise.all:
async function processIdsParallel(ids) {
const promises = ids.map(id => fetchData(id));
const results = await Promise.all(promises);
console.log(results);
}Выбор зависит от задачи: если нужно последовательно обрабатывать каждый элемент (например, записывать в базу по одному), то for...of. Если можно параллельно — Promise.all.
Сравнение с PHP: что общего и в чём разница
В PHP тоже есть async/await, но там он работает через библиотеки (например, ReactPHP) или с помощью генераторов. В PHP 8.1 появились fibers, но они пока не так популярны. В JavaScript async/await — это встроенная возможность, и она используется повсеместно.
| Параметр | JavaScript | PHP |
|---|---|---|
| Встроенная поддержка | Да, с ES2017 | Нет нативно, только через библиотеки |
| Модель выполнения | Однопоточная, событийная | Многопоточная (с расширениями) |
| Обработка ошибок | try...catch | try...catch (в библиотеках) |
| Параллельное выполнение | Promise.all, Promise.race | Через многопоточность или корутины |
Итоговое резюме: что в итоге
Async/await — это мощный инструмент, который делает асинхронный код читаемым и понятным. Он не меняет суть асинхронности, но упрощает её восприятие. Главные правила, которые нужно запомнить:
- Функция с await должна быть объявлена как async.
- Всегда используйте try...catch для обработки ошибок.
- Для параллельных задач используйте Promise.all, а не последовательные await.
- В циклах отдавайте предпочтение for...of или Promise.all с map.
Теперь вы готовы использовать async/await в своих проектах. Начните с малого: замените цепочки .then() на async/await в одном из скриптов, и вы почувствуете разницу. Удачи в кодинге!



