用TypeScript实现高效的异步队列任务管理

在javaScript项目开发中,异步编程是不可或缺的一部分。从网络请求到延时操作,异步操作使得我们能够在等待某个任务完成时继续执行其他任务,提高应用的响应性和性能。然而,随着应用逻辑的复杂化,管理这些异步任务的难度也随之增加。如何有效地组织和控制这些异步任务,成为了开发高效、可维护应用的关键。本文使用JavaScript实现一个异步队列来优雅地管理复杂的异步任务流。

异步编程的挑战

在深入异步队列的实现之前,让我们先回顾一下在JavaScript异步编程中常见的几个挑战:

  1. 回调地狱:过度使用回调函数可能导致代码难以阅读和维护,尤其是当你有多个需要顺序执行的异步操作时。
  2. 并发控制:同时执行多个异步操作时,如何有效地管理它们的完成状态并处理它们的结果。
  3. 错误处理:在异步操作链中适当地捕获和处理错误。

为了解决这些问题,许多开发者转向了Promiseasync/await语法。虽然这些特性极大地改善了异步编程的体验,但在某些场景下,我们仍然需要更细粒度的控制,尤其是当我们需要按顺序执行一系列复杂的异步任务,或者需要在任务间传递数据时。这正是异步队列派上用场的时候。

异步队列(AsyncQueue)的概念

异步队列是一种数据结构,它按照特定的顺序执行异步任务,每个任务在前一个任务完成后开始。这种模式对于需要严格顺序执行的异步操作非常有用,如连续的网络请求,其中后一个请求依赖于前一个请求的结果。

AsyncQueue类设计

AsyncQueue的设计中,我们将关注以下几个关键部分:

  1. 任务的存储和管理:队列需要按顺序存储即将执行的异步任务。
  2. 任务执行的控制:提供方法来开始执行队列中的任务,并在当前任务完成后自动执行下一个任务。
  3. 动态任务管理:允许在队列执行过程中动态添加或移除任务。

实现AsyncQueue

接下来,我们按照先前的概述来具体实现AsyncQueue类。这里用TypeScript实现。

步骤 1: 定义基础结构

首先,我们定义了任务(AsyncTask)的结构,以及异步任务回调(AsyncCallback)的类型。

typescript 复制代码
export type NextFunction = (nextArgs?: any) => void;

export type AsyncCallback = (
  next: NextFunction,
  params: any,
  args: any
) => void;

interface AsyncTask {
  /**
   * 任务uuid
   */
  uuid: number;
  /**
   * 任务开始执行的回调
   * params: push时传入的参数
   * args: 上个任务传来的参数
   */
  callbacks: Array<AsyncCallback>;
  /**
   * 任务参数
   */
  params: any;
}
  • NextFunction是一个函数,当当前任务完成时,它会被调用以触发队列中的下一个任务。
  • AsyncCallback是任务的实际执行函数,它接收next函数、任务参数params以及上一个任务的结果args
  • AsyncTask接口定义了任务的结构,包括唯一标识符uuid、回调函数数组callbacks和任务参数params

步骤 2: 实现AsyncQueue

现在,开始实现AsyncQueue类,它包含了任务队列的核心逻辑。

typescript 复制代码
export class AsyncQueue {
  private _runningAsyncTask: AsyncTask | null = null;
  private static _$uuid_count: number = 1;
  private _queues: Array<AsyncTask> = [];

  public get queues (): Array<AsyncTask> {
    return this._queues;
  }

  private _isProcessingTaskUUID: number = 0;

  private _enable: boolean = true;
  
   /**
   * 任务队列完成回调
   */
  public complete: Function | null = null;

  constructor() {}

  // 方法的具体实现将在后面提供
}

AsyncQueue中,我们使用了以下几个关键属性:

  • _runningAsyncTask: 当前正在执行的任务。
  • _$uuid_count: 用于生成任务的唯一标识符。
  • _queues: 存储待执行任务的队列。
  • _enable: 控制队列是否可以执行任务。
  • complete: 任务队列完成回调

步骤 3: 添加任务到队列

使用push方法向队列中添加单个任务,使用pushMulti添加多个需要并发执行的任务:

typescript 复制代码
/**
   * push一个异步任务到队列中
   * 返回任务uuid
   */
  public push (callback: AsyncCallback, params: any = null): number {
    const uuid = AsyncQueue._$uuid_count++;
    this._queues.push({
      uuid: uuid,
      callbacks: [callback],
      params: params
    });
    return uuid;
  }

  /**
   * push多个任务,多个任务函数会同时执行,
   * 返回任务uuid
   */
  public pushMulti (params: any, ...callbacks: AsyncCallback[]): number {
    const uuid = AsyncQueue._$uuid_count++;
    this._queues.push({
      uuid: uuid,
      callbacks: callbacks,
      params: params
    });
    return uuid;
  }
  • pushpushMulti允许动态地向队列中添加任务,无论是单个任务还是多个任务同时执行。

步骤 4: 移除任务和清空队列

typescript 复制代码
  /** 移除一个还未执行的异步任务 */
  public remove (uuid: number) {
    if (this._runningAsyncTask?.uuid === uuid) {
      console.warn("A running task cannot be removed");
      return;
    }
    for (let i = 0; i < this._queues.length; i++) {
      if (this._queues[i].uuid === uuid) {
        this._queues.splice(i, 1);
        break;
      }
    }
  }  

  /**
   * 清空队列
   */
  public clear () {
    this._queues = [];
    this._isProcessingTaskUUID = 0;
    this._runningAsyncTask = null;
  }
  
  /**
   * 是否有正在处理的任务
   */
  public get isProcessing (): boolean {
    return this._isProcessingTaskUUID > 0;
  }
  • remove方法允许从队列中移除尚未执行的任务,
  • clear方法用于清空整个队列:

步骤 5: 控制队列执行

play方法用于从队列中取出任务并执行。对于单个任务,我们直接调用其回调函数;对于并发任务,我们同时调用它们的回调函数,并等待它们全部完成后才继续, 若队列的大小为0,则代表异步任务队列执行完毕,触发任务队列完成回调complete

typescript 复制代码
  /**
   * 开始运行队列
   */
  public play (args: any = null) {
    if (this.isProcessing) {
      return;
    }

    if (!this._enable) {
      return;
    }

    const actionData: AsyncTask = this._queues.shift()!;
    if (actionData) {
      this._runningAsyncTask = actionData;
      const taskUUID: number = actionData.uuid;
      this._isProcessingTaskUUID = taskUUID;
      const callbacks: Array<AsyncCallback> = actionData.callbacks;

      if (callbacks.length === 1) {
        const nextFunc: NextFunction = (nextArgs: any = null) => {
          this.next(taskUUID, nextArgs);
        };
        callbacks[0](nextFunc, actionData.params, args);
      } else {
        // 多个任务函数同时执行
        let fnum: number = callbacks.length;
        const nextArgsArr: any[] = [];
        const nextFunc: NextFunction = (nextArgs: any = null) => {
          --fnum;
          nextArgsArr.push(nextArgs || null);
          if (fnum === 0) {
            this.next(taskUUID, nextArgsArr);
          }
        };
        const knum = fnum;
        for (let i = 0; i < knum; i++) {
          callbacks[i](nextFunc, actionData.params, args);
        }
      }
    } else {
      this._isProcessingTaskUUID = 0;
      this._runningAsyncTask = null;
      // console.log("任务完成")
      if (this.complete) {
        this.complete(args);
      }
    }
  }

在任务执行完成后,需要一种方式来继续执行队列中的下一个任务。这是通过next方法实现的,该方法将根据当前任务的uuid来确定是否继续:

typescript 复制代码
protected next(taskUUID: number, args: any = null) {
    if (this._isProcessingTaskUUID === taskUUID) {
        this._isProcessingTaskUUID = 0;
        this._runningAsyncTask = null;
        this.play(args);
    }
}

完整的AsyncQueue使用示例

通过AsyncQueue,我们可以很好地管理复杂的异步任务流程。以下是一个使用AsyncQueue的示例:

typescript 复制代码
const queue = new AsyncQueue();

queue.push((next, params) => {
  console.log("执行任务 1");
  // 模拟异步操作
  setTimeout(() => {
    console.log("任务 1 完成");
    next();
  }, 1000);
});

queue.push((next, params) => {
  console.log("执行任务 2");
  setTimeout(() => {
    console.log("任务 2 完成");
    next();
  }, 1000);
});

queue.complete = () => console.log("所有任务执行完毕");

queue.play();

这个简单的例子展示了如何使用AsyncQueue顺序执行两个异步任务,并在所有任务完成后打印一条消息:

shell 复制代码
执行任务 1
任务 1 完成
执行任务 2
任务 2 完成
所有任务执行完毕

至此,实现了一个简单的异步队列任务管理系统AsyncQueue, 它提供了一种高效、灵活的方式来管理和控制异步任务的执行。通过将异步任务封装成队列,可以确保它们按预期的顺序执行,同时保持代码的清晰和可维护性。这种模式特别适用于处理复杂的业务逻辑,如顺序执行网络请求或依赖于前一个任务结果的操作。

相关推荐
j喬乔5 小时前
Node导入不了命名函数?记一次Bug的探索
typescript·node.js
yg_小小程序员16 小时前
vue3中使用vuedraggable实现拖拽
typescript·vue
高山我梦口香糖18 小时前
[react 3种方法] 获取ant组件ref用ts如何定义?
typescript·react
prall20 小时前
实战小技巧:下划线转驼峰篇
前端·typescript
一條狗2 天前
隨筆 20241224 ts寫入excel表
开发语言·前端·typescript
轻口味3 天前
配置TypeScript:tsconfig.json详解
ubuntu·typescript·json
小林rr4 天前
前端TypeScript学习day03-TS高级类型
前端·学习·typescript
web150850966414 天前
前端TypeScript学习day01-TS介绍与TS部分常用类型
前端·学习·typescript
前端熊猫5 天前
省略内容在句子中间
前端·javascript·typescript
禁止摆烂_才浅5 天前
React全家桶 -【高阶函数/高阶组件/钩子】-【forwardRef、mome、useImperativeHandle、useLayoutEffect】
react.js·typescript