Асинхронность в JavaScript — это как готовка на нескольких конфорках: пока варится суп, ты режешь салат, и всё это одновременно. Но если не знать правил, можно устроить пожар на кухне. Эта статья для тех, кто хочет разобраться с колбэками, промисами и async/await без головной боли.
Почему синхронный код — это скучно и медленно
JavaScript — однопоточный язык. Это значит, что он может выполнять только одну задачу за раз. Если ты напишешь код, который ждёт ответа от сервера 5 секунд, вся страница зависнет на эти 5 секунд. Пользователь увидит белый экран и уйдёт. Поэтому и придумали асинхронность: запускаем долгую операцию, не блокируя основной поток, и продолжаем работать.
Представь, что ты заказываешь пиццу. Ты не стоишь у двери, пока её готовят. Ты занимаешься своими делами, а когда курьер звонит — открываешь. Это и есть асинхронность.
Колбэки: первый блин комом
Изначально асинхронные операции обрабатывали с помощью колбэков — функций, которые вызываются после завершения задачи. Выглядит это так:
setTimeout(function() {
console.log('Прошло 2 секунды');
}, 2000);
Проблема в том, что если таких операций много и они зависят друг от друга, получается «callback hell» — адская пирамида из вложенных функций. Код становится нечитаемым, а ошибки ловить всё сложнее.
Промисы: обещание, которое сдерживают
Промис (Promise) — это объект, который представляет результат асинхронной операции. Он может быть в трёх состояниях: ожидание (pending), выполнено (fulfilled) или отклонено (rejected). Работать с ним удобнее: можно вызывать .then() для успеха и .catch() для ошибок.
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка:', error));
Цепочки .then() читаются сверху вниз, как обычный код. Но и у промисов есть свои подводные камни: например, легко забыть про возврат значения или перепутать порядок.
Async/await: синтаксический сахар для промисов
С выходом ES2017 появились async-функции и ключевое слово await. Они позволяют писать асинхронный код так, будто он синхронный. Это самый читаемый и удобный способ на сегодня.
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Ошибка:', error);
}
}
Внутри async-функции можно использовать await перед любым промисом. Код становится линейным и понятным. Но важно помнить: await приостанавливает выполнение только внутри async-функции, не блокируя весь поток.
const [users, posts] = await Promise.all([
fetch('/users').then(res => res.json()),
fetch('/posts').then(res => res.json())
]);
Сравнение подходов: что выбрать
Ниже таблица, которая поможет быстро определиться с инструментом.
| Подход | Плюсы | Минусы |
|---|---|---|
| Колбэки | Простота для одной операции | Адская вложенность, сложно ловить ошибки |
| Промисы | Цепочки, встроенная обработка ошибок | Много .then(), легко запутаться |
| Async/await | Читаемость, синхронный стиль, try/catch | Нужно понимать промисы, нельзя использовать в глобальном контексте (без модулей) |
Обработка ошибок: где подстелить соломку
В асинхронном коде ошибки могут возникнуть в любом месте. В колбэках их обычно передают первым аргументом (error-first callback). В промисах — используют .catch(). В async/await — обычный try/catch.
Но есть нюанс: если забыть обработать отклонённый промис, можно получить UnhandledPromiseRejection. Это как забыть выключить утюг — вроде мелочь, а пожар возможен.
Практические советы для реальных проектов
- Используй async/await везде, где возможно. Это стандарт современного кода.
- Для параллельных запросов применяй Promise.all() или Promise.allSettled().
- Не забывай про отмену запросов: AbortController тебе в помощь.
- Избегай смешивания колбэков и промисов в одном модуле — это сбивает с толку.
- Пиши маленькие async-функции, которые делают одну вещь. Так проще тестировать.
Вот пример с AbortController, который позволяет отменить fetch, если пользователь ушёл со страницы:
const controller = new AbortController();
const signal = controller.signal;
fetch('/data', { signal })
.then(response => response.json())
.catch(err => {
if (err.name === 'AbortError') {
console.log('Запрос отменён');
}
});
// Отмена через 1 секунду
setTimeout(() => controller.abort(), 1000);
Что в итоге
Асинхронность в JavaScript — это не страшно, если знать инструменты. Колбэки уходят в прошлое, промисы дают гибкость, а async/await делает код чистым и понятным. Главное — не забывать про обработку ошибок и параллельное выполнение, где это уместно. Экспериментируй, пиши код, и асинхронность станет твоим другом, а не врагом.
Студия WNDER



