← Назад к вопросам

Как дождаться результата выполнения асинхронной операции?

1.0 Junior🔥 241 комментариев
#Node.js и JavaScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы ожидания результата асинхронной операции

Это одна из самых важных концепций в Node.js. Есть несколько способов дождаться результата, и выбор зависит от контекста и версии JavaScript, которую вы используете.

1. Callbacks (Традиционный способ)

Самый старый, но все еще используемый способ:

fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error('Ошибка:', err);
    return;
  }
  console.log('Данные:', data);
});

console.log('Запрос отправлен, жду...');

Проблемы:

  • "Callback Hell" — вложенность растет когда много операций подряд
  • Сложно обрабатывать ошибки
  • Трудно понять поток выполнения
// Callback Hell (Pyramid of Doom)
fs.readFile('file1.txt', (err, data1) => {
  fs.readFile('file2.txt', (err, data2) => {
    fs.readFile('file3.txt', (err, data3) => {
      // Ужасная вложенность!
    });
  });
});

2. Promises (Обещания)

Получше, чем callbacks. Возвращают объект Promise, который представляет будущий результат:

fs.promises.readFile('file.txt', 'utf8')
  .then(data => {
    console.log('Данные:', data);
  })
  .catch(err => {
    console.error('Ошибка:', err);
  });

console.log('Запрос отправлен');

Состояния Promise:

  • Pending — ожидание
  • Fulfilled — успешно (then)
  • Rejected — ошибка (catch)

Цепочка операций:

fs.promises.readFile('file1.txt', 'utf8')
  .then(data1 => {
    console.log('File 1:', data1);
    return fs.promises.readFile('file2.txt', 'utf8');
  })
  .then(data2 => {
    console.log('File 2:', data2);
    return data1 + data2;
  })
  .catch(err => console.error(err));

3. Async/Await (Современный стандарт)

Синтаксический сахар над Promise. Написано как синхронный код, но работает асинхронно:

async function readFiles() {
  try {
    const data1 = await fs.promises.readFile('file1.txt', 'utf8');
    console.log('File 1:', data1);
    
    const data2 = await fs.promises.readFile('file2.txt', 'utf8');
    console.log('File 2:', data2);
    
    return data1 + data2;
  } catch (err) {
    console.error('Ошибка:', err);
  }
}

// Вызов async функции
readFiles().then(result => console.log('Результат:', result));

Ключевые моменты:

  • await паузирует выполнение функции до получения результата
  • async функция всегда возвращает Promise
  • Ошибки ловятся через try/catch

4. Параллельное выполнение

Promise.all() — все операции одновременно:

async function readAllFiles() {
  try {
    // Все читаются одновременно
    const [data1, data2, data3] = await Promise.all([
      fs.promises.readFile('file1.txt', 'utf8'),
      fs.promises.readFile('file2.txt', 'utf8'),
      fs.promises.readFile('file3.txt', 'utf8'),
    ]);
    
    console.log(data1, data2, data3);
  } catch (err) {
    // Если хотя бы одна операция упадет, ошибка
    console.error(err);
  }
}

Promise.allSettled() — дождитесь всех, даже если одна упадет:

const results = await Promise.allSettled([
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments'),
]);

results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('Успех:', result.value);
  } else {
    console.log('Ошибка:', result.reason);
  }
});

Promise.race() — первый результат:

// Возвращает результат первой выполненной операции
const fastestResponse = await Promise.race([
  fetch('http://server1.com/data'),
  fetch('http://server2.com/data'),
  fetch('http://server3.com/data'),
]);

5. Чтение результата в Express маршруте

import express from 'express';
const app = express();

// ✅ Правильно: async обработчик
app.get('/data', async (req, res) => {
  try {
    const data = await fs.promises.readFile('file.txt', 'utf8');
    res.json({ data });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ❌ Неправильно: без await
app.get('/data', (req, res) => {
  fs.readFile('file.txt', (err, data) => {
    if (err) {
      res.status(500).json({ error: err.message });
      return;
    }
    res.json({ data });
  });
});

6. Практический пример: API запрос

async function fetchUserData(userId: number) {
  try {
    // Дождаемся результата fetch
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    // Дождаемся парсинга JSON
    const user = await response.json();
    
    console.log('Пользователь получен:', user);
    return user;
  } catch (err) {
    console.error('Не удалось загрузить данные:', err);
    throw err;
  }
}

// Использование
await fetchUserData(123);

7. Обработка таймаутов

Когда нужно установить максимальное время ожидания:

function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  return Promise.race([
    promise,
    new Promise<T>((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), ms)
    ),
  ]);
}

try {
  const result = await withTimeout(
    fetch('/api/data'),
    5000 // 5 секунд
  );
} catch (err) {
  console.error('Timeout или ошибка:', err);
}

Сравнение методов

МетодКогда использоватьПлюсыМинусы
CallbacksРедко, legacy кодПростой для одной операцииCallback hell
PromisesИногда, при цепочкахЛучше чем callbacksВсе еще многословно
Async/awaitВсегда (стандарт)Читаемо, как синхронный кодНужен современный JS
Promise.allПараллельные операцииОптимально по производительностиПадает если одна ошибка

Резюме

В современном Node.js (версия 14+) используй async/await как основной инструмент. Это делает код максимально читаемым и легким в поддержке. Promise.all/allSettled используй для параллельных операций.