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

Что использовалось до Promise для передачи запросов на сервер?

2.0 Middle🔥 221 комментариев
#JavaScript Core

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Эпоха до Promise: асинхронность через "ад обратных вызовов"

До появления Promise в стандарте ES6 (2015) работа с асинхронными операциями, такими как HTTP-запросы, в JavaScript полностью строилась на использовании callback-функций и ряда вспомогательных паттернов. Это время часто называют "callback hell" или "пирамидой doom" из-за характерного глубокого вложения функций.

Основные механизмы асинхронных запросов до Promise

1. XMLHttpRequest (XHR) – "классический" AJAX

Это нативный браузерный API, появившийся в начале 2000-х (впервые в Internet Explorer 5). Все библиотеки (включая jQuery.ajax) использовали его под капотом.

// Типичный пример использования XHR
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true); // true = асинхронно

xhr.onreadystatechange = function() {
  // Проверяем состояние запроса
  if (xhr.readyState === 4) { // 4 = DONE
    if (xhr.status === 200) {
      const response = JSON.parse(xhr.responseText);
      console.log('Успех:', response);
      
      // Часто здесь начинался следующий запрос...
      startSecondRequest(response.id, (error, secondData) => {
        if (error) handleError(error);
        else startThirdRequest(secondData.value, (error, thirdData) => {
          // ... и так вглубь
        });
      });
    } else {
      console.error('Ошибка:', xhr.statusText);
    }
  }
};

xhr.onerror = function() {
  console.error('Ошибка сети');
};

xhr.send();

2. Callback-функции как основной паттерн

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

// Типичная сигнатура: callback(error, result)
function fetchData(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = () => {
    if (xhr.status === 200) {
      callback(null, JSON.parse(xhr.responseText));
    } else {
      callback(new Error(`Статус ${xhr.status}`), null);
    }
  };
  xhr.onerror = () => callback(new Error('Ошибка сети'), null);
  xhr.send();
}

// Использование с "адским" вложением
fetchData('/api/users', (error, users) => {
  if (error) return console.error(error);
  
  fetchData(`/api/posts/${users[0].id}`, (error, posts) => {
    if (error) return console.error(error);
    
    fetchData(`/api/comments/${posts[0].id}`, (error, comments) => {
      if (error) return console.error(error);
      console.log('Готово!', comments);
      // Читаемость стремительно падает с каждым уровнем
    });
  });
});

Проблемы подхода на callback-функциях:

  • "Callback hell" – глубокое вложение функций, затрудняющее чтение кода.
  • Сложность обработки ошибок – необходимо проверять ошибки на каждом уровне.
  • Инверсия управления – передача контроля сторонним функциям.
  • Сложность с параллельным выполнением – координация нескольких асинхронных операций требовала дополнительных решений.
  • Сложность с последовательным выполнением цепочек запросов.

Альтернативы и вспомогательные подходы

3. Паттерн "Async.js" или подобные библиотеки

Для борьбы с "callback hell" появились библиотеки вроде Async.js, которые предоставляли управляющие конструкции:

async.series([
  function(callback) { fetchData('/api/step1', callback); },
  function(callback) { fetchData('/api/step2', callback); }
], function(error, results) {
  if (error) console.error(error);
  else console.log('Все операции завершены:', results);
});

// Или параллельное выполнение
async.parallel([
  function(callback) { fetchData('/api/data1', callback); },
  function(callback) { fetchData('/api/data2', callback); }
], function(error, results) {
  // обработка всех результатов сразу
});

4. Событийная модель (Event Emitters)

Некоторые API использовали событийную модель, где вы подписывались на различные события:

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

5. jQuery.Deferred (предшественник Promise)

Библиотека jQuery ввела объект Deferred (с версии 1.5, 2011), который стал прообразом Promise:

// Использование jQuery.ajax с Deferred
$.ajax('/api/data')
  .done(function(response) {
    console.log('Успех:', response);
    return $.ajax('/api/next');
  })
  .done(function(nextResponse) {
    console.log('Второй успех:', nextResponse);
  })
  .fail(function(error) {
    console.error('Ошибка:', error);
  });

Эволюционный путь к Promise

  1. Callback-функции (с 1995) – базовый механизм
  2. Deferred в jQuery (2011) – первая популярная абстракция
  3. Promise/A+ спецификация (2013) – сообщество стандартизировало поведение
  4. Нативные Promise в ES6 (2015) – официальная стандартизация в JavaScript

Ключевые отличия от Promise:

  • Нет цепочки (then().catch()) – только вложенные callback.
  • Сложная обработка ошибок – проверка в каждом callback vs централизованный catch.
  • Нет Promise.all()/Promise.race() – параллельное выполнение требовало самописных решений или библиотек.
  • Гораздо сложнее читать и поддерживать код, особенно при длинных цепочках запросов.

Переход к Promise стал революционным улучшением, позволившим писать асинхронный код, который читается почти как синхронный, а дальнейшее появление async/await в ES2017 сделало работу с асинхронностью ещё более элегантной и выразительной.