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.

Связанные темы

Ресурсы