Перейти к содержимому

Concurrency patterns

JavaScript — однопоточный, но I/O-конкурентный; грамотное управление параллелизмом (семафоры, пулы, очереди с backpressure) — то, что чётко отличает Senior от Mid на техническом собесе.

// Semaphore — ограничение параллелизма
class Semaphore {
#permits;
#queue = [];
constructor(permits) { this.#permits = permits; }
acquire() {
if (this.#permits > 0) { this.#permits--; return Promise.resolve(); }
return new Promise(resolve => this.#queue.push(resolve));
}
release() {
if (this.#queue.length > 0) {
this.#queue.shift()(); // пробуждает следующего ожидающего
} else {
this.#permits++;
}
}
}
const sem = new Semaphore(3); // не более 3 параллельных запросов
async function limitedFetch(url) {
await sem.acquire();
try { return await fetch(url); }
finally { sem.release(); }
}
// p-limit паттерн: concurrency-limited map (аналог p-limit npm)
async function pMap(items, fn, { concurrency = Infinity } = {}) {
const results = new Array(items.length);
let index = 0;
async function worker() {
while (index < items.length) {
const i = index++;
results[i] = await fn(items[i], i);
}
}
await Promise.all(
Array.from({ length: Math.min(concurrency, items.length) }, worker)
);
return results;
}
// Не более 5 одновременных fetch
const data = await pMap(
urls,
url => fetch(url).then(r => r.json()),
{ concurrency: 5 }
);
// Async queue с backpressure через async iterator
class AsyncQueue {
#items = [];
#resolvers = [];
push(item) {
if (this.#resolvers.length > 0) {
this.#resolvers.shift()(item); // будим ожидающего consumer-а
} else {
this.#items.push(item);
}
}
async *[Symbol.asyncIterator]() {
while (true) {
if (this.#items.length > 0) {
yield this.#items.shift();
} else {
yield await new Promise(r => this.#resolvers.push(r));
}
}
}
}
const queue = new AsyncQueue();
setInterval(() => queue.push(Date.now()), 100); // producer
for await (const ts of queue) {
await processTimestamp(ts); // consumer с автоматическим backpressure
}

Итог: Concurrency в JS управляется явными паттернами — семафоры, async iterators, Promise-пулы — всё без многопоточности; ключевое отличие Senior: понимание backpressure и ограничения параллелизма.