Введение в очереди задач в 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