javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > TypeScript任务队列

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

作者:指尖上的生活

在javaScript项目开发中,异步编程是不可或缺的一部分,从网络请求到延时操作,异步操作使得我们能够在等待某个任务完成时继续执行其他任务,提高应用的响应性和性能,本文使用JavaScript实现一个异步队列来优雅地管理复杂的异步任务流,需要的朋友可以参考下

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

异步编程的挑战

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

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

异步队列(AsyncQueue)的概念

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

AsyncQueue类设计

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

实现AsyncQueue

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

步骤 1: 定义基础结构

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

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;
}

步骤 2: 实现AsyncQueue类

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

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中,我们使用了以下几个关键属性:

步骤 3: 添加任务到队列

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

/**
   * 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;
  }

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

  /** 移除一个还未执行的异步任务 */
  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;
  }

步骤 5: 控制队列执行

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

  /**
   * 开始运行队列
   */
  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来确定是否继续:

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

完整的AsyncQueue使用示例

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

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顺序执行两个异步任务,并在所有任务完成后打印一条消息:

执行任务 1
任务 1 完成
执行任务 2
任务 2 完成
所有任务执行完毕

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

以上就是使用TypeScript实现高效的异步队列任务管理的详细内容,更多关于TypeScript任务队列的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文