Очереди задач в TypeScript: полное руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в очереди задач в TypeScript

Очередь задач (Queue) — одна из фундаментальных структур данных в программировании, работающая по принципу FIFO (First In, First Out — «первым пришёл — первым вышел»). Представьте обычную очередь в магазине: первый покупатель, который встал в очередь, обслуживается первым. В программировании очередь задач используется повсеместно: от обработки запросов на сервере до управления асинхронными операциями в браузере.

В этой статье мы разберём, как создавать и использовать очередь задач в TypeScript. Вы узнаете, как типизировать очередь, реализовать основные методы и применить её в реальных проектах. Материал рассчитан на начинающих разработчиков, знакомых с основами TypeScript.

Основные операции с очередью

Любая очередь задач должна поддерживать несколько базовых операций:

  • enqueue (добавить элемент в конец очереди);
  • dequeue (извлечь элемент из начала очереди);
  • peek (посмотреть первый элемент без удаления);
  • isEmpty (проверить, пуста ли очередь);
  • size (узнать количество элементов).

В TypeScript мы можем реализовать очередь на основе массива или связного списка. Для простоты начнём с массива.

Реализация очереди на основе массива

Создадим класс Queue с использованием дженериков — это позволит хранить данные любого типа:

class Queue<T> {
    private items: T[] = [];

    // Добавить элемент в конец очереди
    enqueue(element: T): void {
        this.items.push(element);
    }

    // Удалить и вернуть первый элемент
    dequeue(): T | undefined {
        return this.items.shift();
    }

    // Посмотреть первый элемент без удаления
    peek(): T | undefined {
        return this.items[0];
    }

    // Проверить, пуста ли очередь
    isEmpty(): boolean {
        return this.items.length === 0;
    }

    // Получить размер очереди
    size(): number {
        return this.items.length;
    }
}

Теперь можно создать очередь для любых типов данных:

// Очередь строк
const stringQueue = new Queue<string>();
stringQueue.enqueue('Задача 1');
stringQueue.enqueue('Задача 2');
console.log(stringQueue.dequeue()); // 'Задача 1'

// Очередь чисел
const numberQueue = new Queue<number>();
numberQueue.enqueue(10);
numberQueue.enqueue(20);
console.log(numberQueue.peek()); // 10

Недостатки реализации на массиве

Метод shift() в JavaScript/TypeScript имеет сложность O(n), так как после удаления первого элемента все остальные сдвигаются. Для больших объёмов данных это неэффективно. Решение — реализовать очередь на основе объекта или связного списка.

Оптимизированная очередь с объектом

Чтобы избежать сдвига элементов, будем хранить элементы в объекте и отслеживать индексы начала и конца:

class OptimizedQueue<T> {
    private items: Record<number, T> = {};
    private head: number = 0;
    private tail: number = 0;

    enqueue(element: T): void {
        this.items[this.tail] = element;
        this.tail++;
    }

    dequeue(): T | undefined {
        if (this.isEmpty()) {
            return undefined;
        }
        const item = this.items[this.head];
        delete this.items[this.head];
        this.head++;
        return item;
    }

    peek(): T | undefined {
        return this.items[this.head];
    }

    isEmpty(): boolean {
        return this.tail === this.head;
    }

    size(): number {
        return this.tail - this.head;
    }
}

Теперь операции enqueue и dequeue выполняются за O(1) — константное время, независимо от размера очереди.

Практические примеры использования очередей

1. Очередь задач для асинхронной обработки

Представьте, что вам нужно последовательно обработать несколько запросов к API. Очередь гарантирует, что задачи будут выполняться в порядке поступления:

interface Task {
    id: number;
    name: string;
    execute: () => Promise<void>;
}

class TaskQueue extends OptimizedQueue<Task> {
    async processAll(): Promise<void> {
        while (!this.isEmpty()) {
            const task = this.dequeue();
            if (task) {
                console.log(`Выполняю задачу: ${task.name}`);
                await task.execute();
                console.log(`Задача ${task.name} завершена`);
            }
        }
    }
}

// Использование
const queue = new TaskQueue();
queue.enqueue({
    id: 1,
    name: 'Загрузка данных',
    execute: async () => {
        // имитация запроса
        await new Promise(resolve => setTimeout(resolve, 1000));
    }
});
queue.enqueue({
    id: 2,
    name: 'Сохранение результата',
    execute: async () => {
        await new Promise(resolve => setTimeout(resolve, 500));
    }
});

queue.processAll().then(() => console.log('Все задачи выполнены'));

2. Очередь с приоритетами (Priority Queue)

Иногда задачи нужно обрабатывать не строго в порядке поступления, а с учётом приоритета. Реализуем простую версию:

interface PriorityItem<T> {
    value: T;
    priority: number;
}

class PriorityQueue<T> {
    private items: PriorityItem<T>[] = [];

    enqueue(value: T, priority: number): void {
        const newItem: PriorityItem<T> = { value, priority };
        let added = false;

        for (let i = 0; i < this.items.length; i++) {
            if (this.items[i].priority > priority) {
                this.items.splice(i, 0, newItem);
                added = true;
                break;
            }
        }

        if (!added) {
            this.items.push(newItem);
        }
    }

    dequeue(): T | undefined {
        return this.items.shift()?.value;
    }

    isEmpty(): boolean {
        return this.items.length === 0;
    }
}

// Пример: задачи с разными приоритетами
const priorityQueue = new PriorityQueue<string>();
priorityQueue.enqueue('Низкий приоритет', 3);
priorityQueue.enqueue('Высокий приоритет', 1);
priorityQueue.enqueue('Средний приоритет', 2);

console.log(priorityQueue.dequeue()); // 'Высокий приоритет'
console.log(priorityQueue.dequeue()); // 'Средний приоритет'

3. Ограниченная очередь (Bounded Queue)

Иногда нужно ограничить максимальный размер очереди, чтобы избежать переполнения памяти:

class                

Блоги

Рекомендаций по книгам