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

Что было до Promise?

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

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

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

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

История асинхронного программирования в JavaScript до Promise

До появления Promise в ES6 (2015 году) асинхронное программирование в Node.js и браузерах базировалось на нескольких подходах, каждый со своими недостатками и сильными сторонами.

Callback Hell — эпоха обратных вызовов

Первый и самый примитивный способ работы с асинхронным кодом — это callbacks (обратные вызовы):

// Пример: читаем файл, обрабатываем и пишем результат
const fs = require('fs');

fs.readFile('input.txt', 'utf8', (err1, data) => {
  if (err1) {
    console.error('Ошибка чтения', err1);
    return;
  }

  const processed = data.toUpperCase();

  fs.writeFile('output.txt', processed, (err2) => {
    if (err2) {
      console.error('Ошибка записи', err2);
      return;
    }
    console.log('Готово');
  });
});

Этот подход имел серьёзные проблемы:

  • Callback Hell — глубокая вложенность делает код нечитаемым
  • Сложная обработка ошибок — нужно проверять ошибку на каждом уровне
  • Сложная композиция — объединение асинхронных операций требует боли
  • Утечки памяти — легко забыть释放 ресурсы

Событийная архитектура (EventEmitter)

Node.js предложил EventEmitter как альтернативу callback hell:

const EventEmitter = require('events');
const util = require('util');

function DataFetcher() {
  EventEmitter.call(this);
}
util.inherits(DataFetcher, EventEmitter);

DataFetcher.prototype.fetch = function(url) {
  setTimeout(() => {
    this.emit('data', { result: 'success' });
  }, 1000);
};

const fetcher = new DataFetcher();
fetcher.on('data', (data) => {
  console.log('Получены данные', data);
});
fetcher.on('error', (err) => {
  console.error('Ошибка', err);
});

fetcher.fetch('http://example.com');

Проблемы:

  • Сложная обработка ошибок
  • Невозможно вернуть результат из асинхронной операции
  • Потенциальные race conditions при множественных слушателях

Thunk и частичное применение функций

В функциональном программировании использовались thunks — функции, которые отсрочивают вычисления:

const thunk = () => {
  return someAsyncOperation().then(result => {
    return result * 2;
  });
};

// Позже вызываем
thunk();

Это было основой для генераторов и более сложных систем.

Генераторы (ES6)

Хотя генераторы появились одновременно с Promise, они использовались для эмуляции async/await поведения:

function* fetchData() {
  try {
    const user = yield getUserAsync('123');
    const posts = yield getPostsAsync(user.id);
    console.log('Посты', posts);
  } catch (err) {
    console.error('Ошибка', err);
  }
}

// Требует специального "runner"
function run(generatorFunc) {
  const iterator = generatorFunc();
  
  function handle(result) {
    if (result.done) return Promise.resolve(result.value);
    
    return Promise.resolve(result.value).then(
      res => handle(iterator.next(res)),
      err => iterator.throw(err)
    );
  }
  
  return handle(iterator.next());
}

Библиотеки для управления асинхронностью

До появления Promise разработчики использовали библиотеки вроде:

async.js — утилиты для управления асинхронным кодом:

const async = require('async');

// Последовательное выполнение
async.series([
  callback => setTimeout(() => callback(null, 1), 100),
  callback => setTimeout(() => callback(null, 2), 100)
], (err, results) => {
  console.log(results); // [1, 2]
});

// Параллельное выполнение
async.parallel([
  callback => setTimeout(() => callback(null, 1), 100),
  callback => setTimeout(() => callback(null, 2), 100)
], (err, results) => {
  console.log(results);
});

Q и Bluebird — полноценные реализации Promise-подобных объектов:

const Q = require('q');

function readFile(filename) {
  const deferred = Q.defer();
  
  fs.readFile(filename, 'utf8', (err, data) => {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve(data);
    }
  });
  
  return deferred.promise;
}

readFile('input.txt').then(data => {
  console.log(data);
}).catch(err => {
  console.error(err);
});

Почему Promise стал стандартом

Относительно к прошлым подходам, Promise:

  • Единый интерфейс — стандартизированный API для всех
  • Цепочки операций.then() позволяет элегантно связывать асинхронные операции
  • Обработка ошибок — единая .catch() для всей цепочки
  • КомпозицияPromise.all(), Promise.race(), Promise.allSettled()
  • Совместимость — встроена в язык, не требует внешних библиотек

Это проложило путь к async/await в ES8, который ещё больше упростил асинхронное программирование, позволяя писать асинхронный код как синхронный.