在 TypeScript 中处理任务队列:实践指南
在现代 Web 应用程序中,经常需要异步执行耗时或资源密集型操作,而不阻塞用户主线程。发送邮件简报、生成 PDF 报告、处理上传的图片——这些都是非常适合放入后台任务处理的候选场景。任务队列(task queue)是一种经过验证的架构模式,可以可靠地组织此类操作的执行。在本文中,我们将使用流行的库和方法,详细讲解如何在实践中使用 TypeScript 创建和使用任务队列。
我们将介绍创建简单队列、处理优先级、错误处理,以及通过 BullMQ 库与 Redis 集成。所有示例都将使用 TypeScript 编写,并强调严格的类型化。
1. 基础:创建一个简单的任务队列
首先,我们实现一个不依赖外部库的简单任务队列。这有助于理解基本的工作原理。我们的队列将存储任务函数并按顺序执行它们。
type Task<T> = () => Promise<T>;
interface TaskItem<T> { task: Task<T>; resolve: (value: T | PromiseLike<T>) => void; reject: (reason?: any) => void;}
class SimpleQueue { private queue: TaskItem<any>[] = []; private isProcessing = false;
add<T>(task: Task<T>): Promise<T> { return new Promise<T>((resolve, reject) => { this.queue.push({ task, resolve, reject }); this.process(); }); }
private async process(): Promise<void> { if (this.isProcessing || this.queue.length === 0) return; this.isProcessing = true;
const item = this.queue.shift()!; try { const result = await item.task(); item.resolve(result); } catch (error) { item.reject(error); } finally { this.isProcessing = false; this.process(); } }}
// 使用示例const queue = new SimpleQueue();
queue.add(async () => { console.log('任务 1 开始'); await new Promise(r => setTimeout(r, 1000)); console.log('任务 1 完成'); return '结果 1';});
queue.add(async () => { console.log('任务 2 开始'); return '结果 2';});这个示例展示了基本机制:任务被添加到数组中并按顺序执行。每个任务返回一个 Promise,便于处理结果。然而,这种方法存在局限性:不支持优先级、重试以及在应用程序重启时保存状态。
2. 优先级排序和错误处理
在实际项目中,任务通常具有不同的重要性。例如,发送事务性电子邮件的任务应该比清理临时文件的任务优先执行。让我们为队列添加优先级。
type Priority = 'high' | 'medium' | 'low';
interface PriorityTask<T> { task: Task<T>; priority: Priority; resolve: (value: T) => void; reject: (reason?: any) => void; retries: number;}
class PriorityQueue { private high: PriorityTask<any>[] = []; private medium: PriorityTask<any>[] = []; private low: PriorityTask<any>[] = []; private isProcessing = false; private maxRetries = 3;
add<T>(task: Task<T>, priority: Priority = 'medium'): Promise<T> { return new Promise<T>((resolve, reject) => { const taskItem: PriorityTask<T> = { task, priority, resolve, reject, retries: 0 }; this.getQueue(priority).push(taskItem); this.process(); }); }
private getQueue(priority: Priority): PriorityTask<any>[] { switch (priority) { case 'high': return this.high; case 'medium': return this.medium;