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

Какие были способы ожидания выполнения асинхронной операции до появления async/await?

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

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

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

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

Способы ожидания асинхронной операции до async/await

До появления async/await (ES2017) разработчики использовали несколько подходов для управления асинхронным кодом. Каждый метод имел свои преимущества и недостатки, и эволюция показывает, как язык развивался в сторону более читаемого кода.

1. Callbacks (обратные вызовы)

Callback — это функция, которую вы передаете другой функции, и она вызывается после завершения асинхронной операции. Это был первый способ управления асинхронностью в JavaScript:

// Читаем файл с callback
fs.readFile("file.txt", "utf8", (err, data) => {
  if (err) {
    console.error("Ошибка:", err);
    return;
  }
  console.log("Содержимое:", data);
});

// Пример из Node.js
const fetchUser = (userId, callback) => {
  setTimeout(() => {
    callback(null, { id: userId, name: "John" });
  }, 1000);
};

fetchUser(1, (err, user) => {
  if (err) throw err;
  console.log("User:", user);
});

Проблемы с callbacks:

// ❌ Callback Hell (Pyramid of Doom)
fs.readFile("file1.txt", (err, data1) => {
  if (err) throw err;
  fs.readFile("file2.txt", (err, data2) => {
    if (err) throw err;
    fs.readFile("file3.txt", (err, data3) => {
      if (err) throw err;
      console.log(data1, data2, data3);
    });
  });
});

// Код становится глубоко вложенным (horizontal code)
// Сложно читать, сложно тестировать, сложно обрабатывать ошибки

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

Promise — это объект, который представляет результат асинхронной операции, которая может завершиться успехом (resolve) или ошибкой (reject). Появились в ES2015 и решили многие проблемы callbacks:

// Создание Promise
const fetchUser = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: "John" });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 1000);
  });
};

// Использование Promise
fetchUser(1)
  .then(user => {
    console.log("User:", user);
    return fetchUser(2);
  })
  .then(user => {
    console.log("Second user:", user);
  })
  .catch(err => {
    console.error("Error:", err);
  });

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

  • Цепочки (.then()) вместо вложенности
  • Централизованная обработка ошибок (.catch())
  • Комбинирование нескольких Promise (Promise.all, Promise.race)

Promise.all для параллельного выполнения:

// Выполнить несколько Promise параллельно
Promise.all([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3)
])
  .then(([user1, user2, user3]) => {
    console.log("All users:", user1, user2, user3);
  })
  .catch(err => {
    console.error("One of the requests failed:", err);
  });

Проблемы с Promises:

// ❌ Код все еще читается как "инструкции", не как логика
fetchUser(1)
  .then(user => makeRequest(user.id))
  .then(data => saveToDb(data))
  .then(() => sendEmail())
  .catch(err => handleError(err));

// Проверка ошибок на каждом этапе может быть сложной
// Трудно возвращать значения между .then() блоками

3. Generator Functions (Генераторы)

Generator — это функция, которая может быть приостановлена (yield) и возобновлена позже. Появились в ES2015 и использовались как промежуточное решение:

// Функция-генератор
function* fetchUserGenerator(userId) {
  const user = yield fetchUser(userId);
  console.log("User:", user);
  
  const posts = yield fetchPosts(user.id);
  console.log("Posts:", posts);
  
  return { user, posts };
}

// Нужен runner для управления генератором
const runGenerator = (generatorFn) => {
  const iterator = generatorFn();
  
  const handle = (result) => {
    if (result.done) return Promise.resolve(result.value);
    
    return Promise.resolve(result.value)
      .then(res => handle(iterator.next(res)))
      .catch(err => iterator.throw(err));
  };
  
  return handle(iterator.next());
};

// Использование
runGenerator(fetchUserGenerator);

Проблемы:

  • Требует boilerplate код для runner функции
  • Синтаксис не интуитивен для людей
  • Редко используется в production коде

4. async/await (Современный подход, ES2017)

async/await — это syntactic sugar над Promises, который делает асинхронный код похожим на синхронный:

// Современный способ
async function fetchUserData(userId) {
  try {
    const user = await fetchUser(userId);
    console.log("User:", user);
    
    const posts = await fetchPosts(user.id);
    console.log("Posts:", posts);
    
    return { user, posts };
  } catch (err) {
    console.error("Error:", err);
  }
}

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

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

  • Читается как синхронный код (легче понимать)
  • Обработка ошибок через try/catch (как в синхронном коде)
  • Меньше boilerplate чем Promises
  • Обратная совместимость — async функция возвращает Promise

Параллельное выполнение с async/await:

// ❌ Последовательное выполнение (медленнее)
async function sequentialFetch() {
  const user = await fetchUser(1);    // 1 сек
  const posts = await fetchPosts(user.id);  // 1 сек
  // Всего: 2 секунды
}

// ✅ Параллельное выполнение (быстрее)
async function parallelFetch() {
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts(1)
  ]);
  // Всего: 1 секунда
}

Эволюция подходов

СпособПоявилсяЧитаемостьОбработка ошибокСложность
CallbacksES1❌ НизкаяСложнаяВысокая
PromisesES2015✅ Средняя.catch()Средняя
GeneratorsES2015⚠️ Непонятнаtry/catchВысокая
async/awaitES2017✅✅ Высокаяtry/catchНизкая

Практический пример: эволюция

// 1️⃣ Callbacks (старый способ)
function loadUser(id, callback) {
  fetchUser(id, function(err, user) {
    if (err) callback(err);
    else callback(null, user);
  });
}

// 2️⃣ Promises (промежуточный)
function loadUser(id) {
  return fetchUser(id);
}

loadUser(1)
  .then(user => console.log(user))
  .catch(err => console.error(err));

// 3️⃣ async/await (современный)
async function loadUser(id) {
  try {
    const user = await fetchUser(id);
    console.log(user);
  } catch (err) {
    console.error(err);
  }
}

Итоги

Исторически было несколько способов работать с асинхронностью:

  1. Callbacks — базовый, но неудобный (pyramid of doom)
  2. Promises — лучше (цепочки), но все еще синтаксис .then()
  3. Generators — попытка синхронного синтаксиса (редко используется)
  4. async/await — современное стандартное решение (ES2017+)

Сегодня async/await — это de facto стандарт для асинхронного кода в JavaScript/Node.js. Старые способы все еще могут встречаться в legacy коде, поэтому backend разработчик должен понимать все эти подходы.

Какие были способы ожидания выполнения асинхронной операции до появления async/await? | PrepBro