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.
🎓 Источники
- 🎓 Паттерн Proactor часть 2 как устроен Event Loop · 2025-04-21
- Enqueue с fn и callback вместе
- Бесконечный цикл как в реакторе
- next через offset, без resize
- 🎓 Patterns in Ukrainian: Proactor for JS и TS · 2025-04-21