Queue Pattern
Queue (очередь) — структура данных FIFO (First In, First Out), где элементы добавляются в конец и извлекаются из начала; в JavaScript используется для управления потоком задач, ограничения параллелизма и обработки событий.
Зачем нужно
Очередь управляет порядком выполнения операций. Она нужна для ограничения параллельности запросов к API, последовательного выполнения анимаций, обработки событий без перегрузки системы. Event Loop браузера сам использует очередь задач (task queue).
Где используется
- Очередь запросов к API с ограничением по числу параллельных (rate limiting)
- Очередь задач на обработку (upload файлов, отправка email)
- Отложенная обработка событий
- Job queue в серверных приложениях (Bull, BullMQ)
Основной контент
Базовая реализация Queue
class Queue {
constructor {
this._items = ;
}
enqueue(item) {
this._items.push(item);
}
dequeue {
if (this.isEmpty) throw new Error('Очередь пуста');
return this._items.shift();
}
peek {
if (this.isEmpty) return null;
return this._items[0];
}
isEmpty { return this._items.length === 0; }
size { return this._items.length; }
clear { this._items = ; }
}
const q = new Queue();
q.enqueue('задача 1');
q.enqueue('задача 2');
q.enqueue('задача 3');
console.log(q.peek); // 'задача 1'
console.log(q.dequeue); // 'задача 1'
console.log(q.size); // 2
Асинхронная очередь с параллелизмом
class AsyncQueue {
constructor(concurrency = 1) {
this._concurrency = concurrency;
this._running = 0;
this._queue = ;
}
add(task) {
return new Promise((resolve, reject) => {
this._queue.push({ task, resolve, reject });
this._run;
});
}
_run {
while (this._running < this._concurrency && this._queue.length > 0) {
const { task, resolve, reject } = this._queue.shift();
this._running++;
Promise.resolve
.then( => task)
.then(resolve, reject)
.finally(() => {
this._running--;
this._run; // запустить следующую задачу
});
}
}
}
// Максимум 2 запроса одновременно
const queue = new AsyncQueue(2);
const fetchUrl = (url) => =>
new Promise(resolve => setTimeout(() => {
console.log(`Загружен: ${url}`);
resolve(url);
}, 1000));
queue.add(fetchUrl('url1'));
queue.add(fetchUrl('url2'));
queue.add(fetchUrl('url3')); // подождёт пока url1 или url2 завершится
queue.add(fetchUrl('url4'));
Priority Queue (приоритетная очередь)
class PriorityQueue {
constructor { this._items = ; }
enqueue(item, priority) {
const el = { item, priority };
let added = false;
for (let i = 0; i < this._items.length; i++) {
if (el.priority > this._items[i].priority) {
this._items.splice(i, 0, el);
added = true;
break;
}
}
if (!added) this._items.push(el);
}
dequeue { return this._items.shift()?.item; }
isEmpty { return this._items.length === 0; }
}
const pq = new PriorityQueue();
pq.enqueue('Обычная задача', 1);
pq.enqueue('Критическая', 10);
pq.enqueue('Высокий приоритет', 5);
console.log(pq.dequeue); // 'Критическая'
console.log(pq.dequeue); // 'Высокий приоритет'
console.log(pq.dequeue); // 'Обычная задача'
Частые ошибки
shift— O(n) операция — стандартный массив медленен для больших очередей из-за сдвига элементов. Для высоконагруженных систем используйте deque с двумя указателями или связный список.- Нет ограничения размера — без максимального размера очередь может бесконтрольно расти и занять всю память. Добавляйте
maxSizeи обработку переполнения. - Гонки состояний в асинхронной очереди — при параллельном выполнении несколько задач могут одновременно проверять счётчик. Используйте
_runтолько в.finally.