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

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

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

Начать курс

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

Очередь задач (task queue) — одна из фундаментальных структур данных в программировании, которая работает по принципу "первым пришёл — первым ушёл" (FIFO). В TypeScript очереди задач широко используются для управления асинхронными операциями, обработки запросов, организации фоновых процессов и многих других сценариев.

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

Что такое очередь задач и зачем она нужна?

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

В программировании очереди задач применяются для:

  • Асинхронной обработки — выполнение задач в фоновом режиме без блокировки основного потока
  • Управления нагрузкой — ограничение количества одновременно выполняемых операций
  • Обработки событий — последовательная обработка входящих запросов
  • Планирования задач — выполнение задач с задержкой или по расписанию

Реализация базовой очереди задач в TypeScript

Создание класса очереди

Начнём с простой реализации очереди на TypeScript. Создадим класс TaskQueue, который поддерживает базовые операции:

// task-queue.ts
type Task<T> = () => Promise<T>;

class TaskQueue<T> {
    private queue: Task<T>[] = [];
    private isProcessing = false;

    // Добавление задачи в очередь
    enqueue(task: Task<T>): void {
        this.queue.push(task);
        if (!this.isProcessing) {
            this.processQueue();
        }
    }

    // Обработка очереди
    private async processQueue(): Promise<void> {
        this.isProcessing = true;

        while (this.queue.length > 0) {
            const task = this.queue.shift()!;
            try {
                const result = await task();
                console.log('Задача выполнена:', result);
            } catch (error) {
                console.error('Ошибка при выполнении задачи:', error);
            }
        }

        this.isProcessing = false;
    }

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

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

Использование очереди

Теперь посмотрим, как использовать наш класс на практике:

// example.ts
const taskQueue = new TaskQueue<string>();

// Создаём несколько асинхронных задач
const task1 = async () => {
    await delay(1000);
    return 'Первая задача выполнена';
};

const task2 = async () => {
    await delay(500);
    return 'Вторая задача выполнена';
};

const task3 = async () => {
    await delay(200);
    return 'Третья задача выполнена';
};

// Добавляем задачи в очередь
taskQueue.enqueue(task1);
taskQueue.enqueue(task2);
taskQueue.enqueue(task3);

// Вспомогательная функция
function delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Результат:
// Задача выполнена: Первая задача выполнена (через 1с)
// Задача выполнена: Вторая задача выполнена (через 1.5с)
// Задача выполнена: Третья задача выполнена (через 1.7с)

Продвинутая очередь с приоритетами

В реальных проектах часто требуется обрабатывать задачи с разным приоритетом. Давайте расширим нашу очередь, добавив поддержку приоритетов:

// priority-queue.ts
type Priority = 'high' | 'medium' | 'low';

interface TaskItem<T> {
    task: () => Promise<T>;
    priority: Priority;
    id: number;
}

class PriorityTaskQueue<T> {
    private highQueue: Task<T>[] = [];
    private mediumQueue: Task<T>[] = [];
    private lowQueue: Task<T>[] = [];
    private isProcessing = false;
    private taskId = 0;
    private maxConcurrent: number;

    constructor(maxConcurrent: number = 1) {
        this.maxConcurrent = maxConcurrent;
    }

    enqueue(task: () => Promise<T>, priority: Priority = 'medium'): void {
        const taskItem: TaskItem<T> = {
            task,
            priority,
            id: ++this.taskId
        };

        switch (priority) {
            case 'high':
                this.highQueue.push(taskItem.task);
                break;
            case 'medium':
                this.mediumQueue.push(taskItem.task);
                break;
            case 'low':
                this.lowQueue.push(taskItem.task);
                break;
        }

        if (!this.isProcessing) {
            this.processQueue();
        }
    }

    private async processQueue(): Promise<void> {
        this.isProcessing = true;
        let activeTasks = 0;

        while (this.hasTasks()) {
            const task = this.getNextTask();
            if (!task) break;

            activeTasks++;
            task().then(() => {
                activeTasks--;
                if (activeTasks < this.maxConcurrent) {
                    this.processQueue();
                }
            }).catch(error => {
                console.error('Ошибка:', error);
                activeTasks--;
            });

            if (activeTasks >= this.maxConcurrent) {
                break;
            }
        }

        if (activeTasks === 0) {
            this.isProcessing = false;
        }
    }

    private hasTasks(): boolean {
        return this.highQueue.length > 0 ||
               this.mediumQueue.length > 0 ||
               this.lowQueue.length > 0;
    }

    private getNextTask(): (() => Promise<T>) | null {
        if (this.highQueue.length > 0) {
            return this.highQueue.shift()!;
        }
        if (this.mediumQueue.length > 0) {
            return this.mediumQueue.shift()!;
        }
        if (this.lowQueue.length > 0) {
            return this.lowQueue.shift()!;
        }
        return null;
    }
}

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

// priority-example.ts
const priorityQueue = new PriorityTaskQueue<string>(2);

// Задачи с разными приоритетами
priorityQueue.enqueue(async () => {
    await delay(1000);
    return 'Срочная задача';
}, 'high');

priorityQueue.enqueue(async () => {
    await delay(500);
    return 'Обычная задача';
}, 'medium');

priorityQueue.enqueue(async () => {
    await delay(200);
    return 'Фоновая задача';
}, 'low                

Блоги

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