Proactor Pattern — Проактор

Развитие Reactor: в очередь кладётся функция + её колбек, а не просто handler. Обработка асинхронных операций с уже готовыми результатами.

Проблема

Reactor отдаёт уведомления о готовности ("сокет готов к чтению"). Дальше код должен сам читать данные. Это дважды-проход: «готов» → «читай» → данные.

Хочется проще: получить событие с результатом сразу ("вот данные, которые ты ждал").

Решение

В очередь кладём пары: { fn, callback }.

  • fn — асинхронная операция
  • callback — что вызвать с результатом

Цикл вызывает fn, передавая ему свой callback. Когда fn завершается — её callback вызывает наш callback с результатом.

Пример в JS

class Proactor {
  tasks = ;

  enqueue(fn, callback) {
    this.tasks.push({ fn, callback });
  }

  start {
    if (!this.tasks.length) return;
    this.next(this.tasks, 0);
  }

  next(tasks, offset) {
    if (offset >= tasks.length) return;
    const { fn, callback } = tasks[offset];
    fn((result) => {
      callback(result);
      this.next(tasks, offset + 1);  // продолжаем по offset, без shift
    });
  }
}

// использование
const p = new Proactor();
p.enqueue(
  (cb) => setTimeout( => cb('data1'), 100),
  (res) => console.log('got', res),
);
p.start();

Где используется в JS-экосистеме

  • libuv в Node.js на Windows — Proactor через IOCP (I/O Completion Ports)
  • libuv на Linux/Mac — Reactor (epoll/kqueue) + thread pool
  • fs.readFile — Proactor-семантика: вернёт данные, не «готовность»
  • Boost.Asio в C++ — Proactor pattern
  • Promise-based API — все имеют семантику Proactor (результат, не готовность)

Подводные камни

  • Proactor сложнее Reactor: нужно следить за parking/wake-up tasks.
  • Offset вместо shift: удаление из массива → resize → плохо для производительности. Лучше держать offset.
  • Cancellation сложно: как отменить задачу, которая уже в работе?
  • Backpressure: если producer быстрее consumer'а, очередь растёт.

Главные тезисы автора

  • «Из синхронного кода делаем асинхронный, эмулируем асинхронное поведение, сами крутим бесконечный цикл».
  • Главное отличие Proactor от Reactor: в очередь кладём fn + callback вместе.
  • fn — асинхронная, ожидает свой callback.
  • Наш callback вставляет своё поведение между fn и user-кодом.
  • Offset вместо delete — оптимизация: не ресайзить массив.
  • Часть 2 серии — смотреть после Reactor.

🎓 Источники

См. также