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

В чем преимущество async/await и промисов перед callback в Node.js?

2.0 Middle🔥 131 комментариев
#Node.js и JavaScript

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

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

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

В чем преимущество async/await и промисов перед callback в Node.js?

Это эволюция асинхронного кода в JavaScript. Каждый этап приносит улучшения в читаемость, обработку ошибок и управление потоком исполнения.

Этап 1: Callbacks (Адский ад)

// Callback Hell / Pyramid of Doom
fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error(err);
  } else {
    db.query('SELECT * FROM users', (err, users) => {
      if (err) {
        console.error(err);
      } else {
        users.forEach(user => {
          const message = `User: ${user.name}`;
          notify.send(message, (err, result) => {
            if (err) {
              console.error(err);
            } else {
              console.log('Notification sent');
            }
          });
        });
      }
    });
  }
});

Проблемы:

  • Невероятно сложно читать
  • Обработка ошибок дублируется везде
  • Трудно контролировать поток
  • Hell callback: каждый уровень добавляет отступ

Этап 2: Promises (Лучше, но сложно)

fs.promises.readFile('file.txt', 'utf-8')
  .then(data => {
    return db.query('SELECT * FROM users');
  })
  .then(users => {
    const promises = users.map(user => {
      const message = `User: ${user.name}`;
      return notify.send(message);
    });
    return Promise.all(promises);
  })
  .then(() => {
    console.log('All notifications sent');
  })
  .catch(err => {
    console.error('Error:', err);
  });

Улучшения:

  • Линейный поток кода
  • Единая обработка ошибок (catch)
  • Можно использовать Promise.all, Promise.race
  • Лучше читается

Проблемы:

  • Всё ещё не очень интуитивно
  • Promise chains становятся сложными
  • Трудно понять логику при 5+ .then()

Этап 3: async/await (Почти как синхронный код)

async function processData() {
  try {
    const data = await fs.promises.readFile('file.txt', 'utf-8');
    const users = await db.query('SELECT * FROM users');
    
    for (const user of users) {
      const message = `User: ${user.name}`;
      await notify.send(message);
    }
    
    console.log('All notifications sent');
  } catch (err) {
    console.error('Error:', err);
  }
}

processData();

Преимущества:

  • Выглядит как обычный синхронный код
  • Легко читать и понимать
  • try/catch как в синхронном коде
  • Не нужно .then() цепочки

Сравнительная таблица

ПараметрCallbackPromiseasync/await
ЧитаемостьУжаснаХорошаяОтличная
Обработка ошибокПовторяется вездеЕдиная (catch)try/catch
Логика потокаЗапутанаПонятнаМаксимально понятна
DebuggingСложноСреднеЛегко
PerformanceХорошХорошИдентичен Promise

Практический пример: Обработка ошибок

Callback (ПЛОХО):

function fetchUser(id, callback) {
  getUserFromDB(id, (err, user) => {
    if (err) {
      // Какую-то ошибку потеряем
      callback(err);
      return;
    }
    
    getPostsByUser(user.id, (err, posts) => {
      if (err) {
        // Снова обработка
        callback(err);
        return;
      }
      
      callback(null, { user, posts });
    });
  });
}

Promise (ЛУЧШЕ):

function fetchUser(id) {
  return getUserFromDB(id)
    .then(user => {
      return getPostsByUser(user.id)
        .then(posts => ({ user, posts }));
    })
    .catch(err => {
      // Единая обработка всех ошибок
      console.error('Fetch failed:', err);
      throw err;
    });
}

async/await (ЛУЧШЕЕ):

async function fetchUser(id) {
  try {
    const user = await getUserFromDB(id);
    const posts = await getPostsByUser(user.id);
    return { user, posts };
  } catch (err) {
    console.error('Fetch failed:', err);
    throw err;
  }
}

Асинхронные операции в цикле

Callback (невозможно читать):

const results = [];
let processed = 0;

userIds.forEach(id => {
  getUser(id, (err, user) => {
    if (err) {
      // Обработка ошибок...
      return;
    }
    results.push(user);
    processed++;
    
    if (processed === userIds.length) {
      callback(null, results);
    }
  });
});

Promise:

const promises = userIds.map(id => getUser(id));

Promise.all(promises)
  .then(users => {
    callback(null, users);
  })
  .catch(err => {
    callback(err);
  });

async/await (ИДЕАЛЬНО):

async function getUsers() {
  try {
    const users = await Promise.all(
      userIds.map(id => getUser(id))
    );
    return users;
  } catch (err) {
    console.error('Failed:', err);
    throw err;
  }
}

Основные преимущества async/await

  1. Синхронный стиль — код выглядит как обычный синхронный
  2. try/catch — знакомая обработка ошибок
  3. Debugger friendly — легче отлаживать
  4. Меньше кода — не нужны .then() цепочки
  5. Понятнее логика — новичкам проще понять
  6. Переменные — можно использовать переменные между await'ами

Важные моменты

// ✓ async/await это синтаксический сахар над Promise
const x = await promise;
// Эквивалентно
const x = await promise.then(result => result);

// ✓ Ошибка в async функции становится rejected Promise
async function test() {
  throw new Error('Oops');
}
test().catch(err => console.error(err));

// ✗ Не забывайте await
async function bad() {
  getUser(id);  // ✗ Promise не ждём
}

async function good() {
  await getUser(id);  // ✓ Ждём результат
}

Вывод

  • Callbacks — исторически первый подход, очень запутанный
  • Promises — улучшение, но всё ещё нужны .then() цепочки
  • async/await — современный стандарт, почти синхронный код

В 2025 году используй async/await везде (если браузер поддерживает, а Node.js 10+ поддерживает 100%).

В чем преимущество async/await и промисов перед callback в Node.js? | PrepBro