Actor Pattern — Модель акторов
Изолированные сущности с состоянием, обменивающиеся событиями. В JS не встроено, но реализуется через очереди + async + строгую изоляцию state. Согласно автору — Алан Кей изначально подразумевал ООП именно как акторы.
Проблема
В асинхронном коде с await shared state нарушается. Пример: 5 заказов параллельно тратят товар со склада, у каждого до decrement срабатывает свой await — все продали один и тот же товар, склад ушёл в -5.
Решение
- Один actor = единственный владелец state
- Все запросы к state идут через очередь сообщений актора
- Актор обрабатывает их последовательно (одна задача за раз)
- Снаружи — async-API (await на response), внутри — синхронная обработка
Пример в JS
class StockActor {
#state; // приватный, никто извне не трогает
#queue = ;
#processing = false;
constructor(initial) { this.#state = new Map(initial); }
send(message) {
return new Promise((resolve, reject) => {
this.#queue.push({ message, resolve, reject });
this.#process;
});
}
async #process {
if (this.#processing) return;
this.#processing = true;
while (this.#queue.length) {
const { message, resolve, reject } = this.#queue.shift();
try {
resolve(await this.#handle(message));
} catch (e) { reject(e); }
}
this.#processing = false;
}
async #handle(msg) {
if (msg.type === 'buy') {
const qty = this.#state.get(msg.item) ?? 0;
if (qty < msg.qty) throw new Error('out of stock');
this.#state.set(msg.item, qty - msg.qty);
return 'ok';
}
}
}
// клиенты обращаются через очередь — race conditions невозможны
const stock = new StockActor([['apple', 10]]);
await Promise.all([
stock.send({ type: 'buy', item: 'apple', qty: 3 }),
stock.send({ type: 'buy', item: 'apple', qty: 5 }),
]);
Где используется в JS-экосистеме
- Web Workers — каждый воркер изолирован, общение через
postMessage - worker_threads в Node.js — то же на бэке
- Erlang-style libraries:
actor-system,js-csp - Redux saga — конкурентные задачи через generator+channels
- XState v5 actors — actor model в state machines
- WebSocket-сервер: каждый коннект как актор
Подводные камни
- Узкое место: если актор обрабатывает много задач — становится bottleneck.
- Composability: акторы плохо комбинируются — нужны actor-systems (как Erlang OTP).
- Backpressure: очередь может расти бесконечно — нужны лимиты.
- State не сериализуется между worker_threads автоматически —
postMessageкопирует.
Главные тезисы автора
- «Алан Кей под ООП имел в виду модель акторов» — изолированные сущности, события.
- JS sync-код поточно-безопасен, но с
awaitownership state ломается. - «Ownership state через async/await» — иллюзорный, ломается при concurrent вызовах.
- «State лучше внутри функции» — не расшаривать между контекстами.
- Гонка: все проверки наличия идут до списания, поэтому проверка не спасает.
- Actor + State + Observer — частая комбинация в распределённых системах.
🎓 Источники
- 🎓 Actor Pattern – Asynchronous Programming · 2025-04-09
- Sync JS безопасен, async с shared state — нет
- Пример склада с гонкой
- Ownership через await — иллюзорный
- 🎓 Модель акторов для параллельных вычислений · 2019-05-16
- 🎓 Actor Compose (композиция актора и состояния) · 2025-07-19
- 🎓 SOLID, GRASP, GoF и функциональное программирование · 2025-11-15
- Алан Кей и модель акторов как корень ООП