Вступление
Node.js — крут во многом благодаря своей способности не тормозить выполнение, работая с задачами асинхронно. Но как разобраться с этой асинхронностью? Если в примере с сервером мы обошлись простым колбэком, то в реальной жизни обычно хочется чего-то помощнее. Щас посмотрим, как развивался асинхронный код: от колбэчного ада до классных промисов и удобного async/await.
Проблема ад колбэков:
// Классическая проблема вложенных колбэков readFile('file1.txt', (err, data1) => { if (err) throw err; readFile('file2.txt', (err, data2) => { if (err) throw err; writeFile('result.txt', data1 + data2, (err) => { if (err) throw err; console.log('Готово!'); }); }); });
Решение 1: Промисы (Promises)
Промисы – это штуки, которые показывают, чем закончилась асинхронная операция (успехом или провалом).
Что важно знать:
Состояния: pending (в процессе), fulfilled (готово), rejected (отклонено)
Методы: .then() – для обработки успехов, .catch() – для ловли ошибок
Цепочки: позволяют выполнять асинхронные штуки по очереди
Пример: Читаем файл с помощью промисов.
// Предположим, что readFilePromise возвращает промис const fs = require('fs').promises; fs.readFile('config.json', 'utf8') .then(data => { console.log('Файл прочитан успешно'); const config = JSON.parse(data); return config; // Передаем в следующий then }) .then(config => { console.log('Имя приложения:', config.appName); return fs.writeFile('output.log', `Конфиг загружен: ${new Date()}`); }) .then(() => { console.log('Лог записан'); }) .catch(error => { console.error('Произошла ошибка:', error.message); });
Решение 2: Async/Await — современный подход
Синтаксис async/await делает асинхронный код похожим на синхронный, что упрощает его чтение и поддержку.
Ключевые моменты:
-
asyncфункция всегда возвращает промис -
awaitможно использовать только внутриasyncфункций -
Код выполняется «сверху вниз», что интуитивно понятнее
Пример: Практическое использование async/await
const fs = require('fs').promises; const axios = require('axios'); // популярная библиотека для HTTP-запросов async function fetchUserData(userId) { try { // 1. Читаем локальный файл с настройками const settings = await fs.readFile('settings.json', 'utf8'); const { apiUrl } = JSON.parse(settings); // 2. Делаем HTTP-запрос к API console.log('Загружаем данные пользователя...'); const response = await axios.get(`${apiUrl}/users/${userId}`); // 3. Обрабатываем полученные данные const user = response.data; user.lastAccess = new Date(); // 4. Сохраняем обновленные данные await fs.writeFile( `user-${userId}.json`, JSON.stringify(user, null, 2) ); console.log(`Данные пользователя ${user.name} сохранены!`); return user; } catch (error) { console.error('Ошибка в процессе загрузки:', error.message); // Можно вернуть значение по умолчанию или пробросить ошибку дальше throw error; } } // Использование функции async function main() { try { const user = await fetchUserData(123); console.log('Работа завершена успешно'); } catch (error) { console.log('Не удалось выполнить операцию'); } } main();
Полезные паттерны
Параллельное выполнение:
async function loadMultipleResources() { // Запускаем все промисы параллельно const [users, posts, settings] = await Promise.all([ fetch('/api/users'), fetch('/api/posts'), fetch('/api/settings') ]); // Все данные загружены параллельно! return { users, posts, settings }; }
Обработка нескольких попыток:

