Skip to content

Instantly share code, notes, and snippets.

@lilBunnyRabbit
Last active February 18, 2025 12:28
Show Gist options
  • Select an option

  • Save lilBunnyRabbit/8ba9a800b7cf9f91df84e15e9a2d6faa to your computer and use it in GitHub Desktop.

Select an option

Save lilBunnyRabbit/8ba9a800b7cf9f91df84e15e9a2d6faa to your computer and use it in GitHub Desktop.
Task Management System in Typescript

TypeScript Task Management System

Important

The task manager functionality previously available in this gist has been officially integrated into the @lilbunnyrabbit/task-manager package. For continued support and access to the latest features, please refer to the package.

This TypeScript project implements a sophisticated task management system, designed to facilitate the creation, execution, and monitoring of asynchronous and synchronous tasks in a structured and efficient manner. The system is built around two primary components: TaskManager and Task.

TaskManager serves as the central orchestrator for managing multiple tasks. It provides capabilities to queue tasks, control their execution (either sequentially or in parallel), and monitor their overall progress and status. This makes it ideal for scenarios where tasks need to be executed in a specific order or concurrently, with the ability to handle dependencies and execution flows dynamically.

The Task class represents individual units of work. Each task encapsulates its own logic, data, and execution state. Tasks are highly configurable, allowing for custom execution logic and error handling.

The system is designed to be flexible and adaptable, suitable for various use cases ranging from simple background data processing to complex workflow management in web applications. The event-driven architecture of both TaskManager and Task ensures that users can easily hook into different stages of task execution for logging, monitoring, or altering the flow based on dynamic conditions.

Note
This project requires EventEmitter and Optional to work.

Usage

Creating a Task

To create a new task, define it using the createTask function with the required configuration.

import { createTask } from "./Task";

interface Data {
  name: string;
}

interface Result {
  id: string;
  name: string;
}

const createUserTask = createTask<Data, Result>({
  name: "Create User",

  parse() {
    const status = () => {
      switch (this.status) {
        case "idle":
          return `${this.name} with value ${this.data}`;
        case "in-progress":
          return "Creating user...";
        case "error":
          return "Failed to create user...";
        case "success":
          return `User created #(${this.result.get()!.id})`;
      }
    };

    return {
      status: status(),
      result: this.result.isPresent() ? JSON.stringify(this.result.get()) : undefined,
    };
  },

  async execute(data) {
    const user = await someApiCall(data);

    return user;
  },
});

Managing Tasks with TaskManager

Use the TaskManager class to add, execute, and monitor tasks.

import { TaskManager } from "./TaskManager";

const taskManager = new TaskManager();
taskManager.addTasks([createUserTask({ name: "Foo" })]);
taskManager.start();

Full Example

Define Tasks

import { createTask } from "./Task";

// tasks/createObject.ts
export default createTask<number, { value: number }>({
    name: "Create Object",

    async execute(value) {
        if (value > 100) {
            this.addWarning(`Value "${value}" is > 100.`);
        }

        return { value };
    },
});

// tasks/sumObjects.ts
import createObjectTask from "./createObject";

export default createTask<void, number>({
    name: "Sum Objects",

    async execute() {
        const objects = this.manager.getTasksResults(createObjectTask);

        if (!objects.length) {
            throw new Error(`Requires at least one task by ${createObjectTask}`);
        }

        let sum = 0;

        for (let i = 0; i < objects.length; i++) {
            sum += objects[i].value;

            this.setProgress(i / objects.length);
        }

        return sum;
    },
});

// tasks/averageObjects.ts
import createObjectTask from "./createObject";
import sumObjectsTask from "./sumObjects";

export default createTask<void, number>({
    name: "Average Objects",

    async execute() {
        const tasks = this.manager.findTasks(createObjectTask);

        const sum = this.manager.getLastTaskResult(sumObjectsTask);

        return sum / tasks.length;
    },
});

// tasks.ts
import createObjectTask from "./createObject";
import sumObjectsTask from "./sumObjects";
import averageObjectsTask from "./averageObjects";

export const ExampleTasks = (values: number[]) => [
    ...values.map((value) => {
        return createObjectTask(value);
    }),
    sumObjectsTask(),
    averageObjectsTask(),
];

Use Tasks

import { TaskManager } from "./TaskManager";
import { ExampleTasks } from "./tasks";

const taskManager = new TaskManager((manager) => {
    manager.addTasks(ExampleTasks([1, 2, 3]));

    // Execution will continue even if the task fails
    manager.removeFlag(TaskManager.Flag.FAIL_ON_ERROR);
});

taskManager.on("change", function () {
    console.log("TaskManager change", this);
});

taskManager.start();

React Example

import React from "react";
import { TaskManager } from "./TaskManager";
import { ExampleTasks } from "./tasks";

export function useTaskManager(initialSetup?: (manager: TaskManager) => void) {
  const [setup] = React.useState<typeof initialSetup>(() => initialSetup);

  const [instance, setInstance] = React.useState<TaskManager | null>(null);
  const [updatedAt, setUpdatedAt] = React.useState(Date.now());

  React.useEffect(() => {
    const onChange = () => {
      setUpdatedAt(Date.now());
    };

    const manager = new TaskManager().on("change", onChange);
    setInstance(manager);

    setup?.(manager);

    return () => {
      manager.off("change", onChange);
      setInstance(null);
    };
  }, [setup]);

  return {
    manager: instance,
    deps: [instance, updatedAt] as React.DependencyList,
  };
}

export const TaskManagerPreview: React.FC = () => {
  const { manager } = useTaskManager((manager) => {
    manager.addTasks(ExampleTasks([1, 2, 3]));

    // Enables parallel execution
    manager.addFlag(TaskManager.Flag.PARALLEL_EXECUTION);
  });

  return (
    <div>
      <button onClick={() => manager.start()}>Start</button>

      {manager.tasks.map((task) => JSON.stringify(task.parse()))}

      {manager.queue.map((task) => JSON.stringify(task.parse()))}
    </div>
  )
}

API

TaskManager

The TaskManager class is responsible for managing the execution and lifecycle of tasks.

Properties

Name Description
status Current status.
progress Current progress of tasks.
flags Current set of flags.

Methods

Name Description
addTasks(tasks) Adds an array of tasks to the task queue.
start([force]) Starts the execution of tasks in the task manager.
stop() Stops the execution of tasks.
reset() Resets the task manager to its initial state.
clearQueue() Clears the task queue.
findTask(taskBuilder) Finds a task in the list of tasks.
getTask(taskBuilder) Retrieves a task from the list of tasks or throws.
getTaskResult(taskBuilder) Retrieves the result of a task or throws.
findLastTask(taskBuilder) Finds the last task of a specific type in the list of tasks.
getLastTask(taskBuilder) Retrieves the last task of a specific type or throws.
getLastTaskResult(taskBuilder) Retrieves the result of the last task of a specific type or throws.
findTasks(taskBuilder) Finds all tasks of a specific type.
getTasksResults(taskBuilder) Retrieves the results of all tasks of a specific type or throws.

Flags

The TaskManager utilizes a set of flags to control its behavior during task execution. These flags are part of the TaskManager.Flag enum and can be set or checked to modify how the TaskManager handles tasks.

Name Default Description
STOP No Flag indicating that the execution loop should stop.
FAIL_ON_ERROR Yes Flag indicating that the execution should stop if any task fails.
PARALLEL_EXECUTION No Flag for enabling parallel execution of tasks.

Task

Represents an individual task within the task manager system.

Properties

Name Description
id Unique identifier of the task.
name Name of the task.
data Input data for the task.
manager TaskManager instance to which this task is bound.

Methods

Name Description
execute() Executes the task.
parse() Parses the task, providing a representation suitable for UI rendering.
toString() Returns a string representation of the Task instance.
clone() Creates a clone of the current task.

isTask(task, taskBuilder)

The isTask function is a type guard used to check if a given object is an instance of the Task class and was created with the provided TaskBuilder.

createTask(config)

The createTask function is a factory method used to create a new TaskBuilder. The TaskBuilder is a specialized function that, when called, constructs and returns an instance of the Task class based on the provided configuration.

Conclusion

The task management system offers a robust and flexible solution for managing tasks in TypeScript applications. Its design caters to various scenarios where task scheduling and execution are critical.

import { EventEmitter } from "./EventEmitter";
import { Optional } from "./Optional";
import { TaskManager } from "./TaskManager";
/**
* Configuration interface for creating a `Task`.
*
* @template TData - Type of input data for the task.
* @template TResult - Type of result produced by the task.
* @template TError - Type of error that may be thrown by the task.
*/
export interface TaskConfig<TData, TResult, TError> {
/**
* Name of the task.
*/
name: string;
/**
* Function to parse the task's outcome.
*/
parse?: (this: Task<TData, TResult, TError>) => Task.Parsed;
/**
* The core function to execute the task. It should return a result or a promise of the result.
*/
execute: (this: Task<TData, TResult, TError>, data: TData) => TResult | Promise<TResult>;
}
/**
* Base class for a `Task`, handling core functionalities like status, progress, and error management.
* Emits events on status and progress changes.
*
* @template TResult - The type of the result that the task produces.
* @template TError - The type of error that the task may encounter.
*/
class TaskBase<TResult, TError> extends EventEmitter<{
/**
* Emits on general change.
*/
change: void;
/**
* Emits on task progress.
*/
progress: number;
}> {
// Status
/**
* Current status.
*/
protected _status: Task.Status = "idle";
/**
* Current status.
*/
public get status() {
return this._status;
}
/**
* Set the status.
*
* @emits change
*/
public set status(status: typeof this._status) {
if (status !== this.status) {
this._status = status;
this.emit("change");
}
}
/**
* Sets the status.
*
* @param status - The status to set.
* @returns The instance of the task.
*/
public setStatus(status: typeof this._status): this {
this.status = status;
return this;
}
// Progress
/**
* Current progress of the task ([0, 1]).
*/
protected _progress: number = 0;
/**
* Current progress of the task ([0, 1]).
*/
public get progress() {
return this._progress;
}
/**
* Sets the progress.
*
* @emits progress
* @emits change
*/
public set progress(progress: typeof this._progress) {
const validProgress = progress > 1 ? 1 : progress < 0 ? 0 : progress;
if (validProgress !== this._progress) {
this._progress = validProgress;
this.emit("progress", validProgress).emit("change");
}
}
/**
* Sets the progress.
*
* @param progress - The progress to set.
* @returns The instance of the task.
*/
public setProgress(progress: typeof this._progress) {
this.progress = progress;
return this;
}
// Errors
/**
* The list of errors encountered during the task's execution.
*/
protected _errors?: TError[];
/**
* The list of errors encountered during the task's execution.
*/
public get errors() {
return this._errors;
}
/**
* Set the list of errors.
*
* @param errors - Array of errors to set.
* @emits change
*/
public set errors(errors: typeof this._errors) {
this._errors = errors;
this.emit("change");
}
/**
* Adds one or more errors to the task and changes its status to 'error'.
*
* @param errors - The errors to add to the task.
* @returns The instance of the task.
* @emits change
*/
public addError(...errors: NonNullable<typeof this._errors>) {
if (errors.length) {
if (!this.errors) this._errors = [];
this._errors!.push(...errors);
this._status = "error";
this.emit("change");
}
return this;
}
// Warnings
/**
* The list of warnings generated during the task's execution.
*/
protected _warnings?: string[];
/**
* The list of warnings generated during the task's execution.
*/
public get warnings() {
return this._warnings;
}
/**
* Set the list of warnings.
*
* @param warnings - Array of warnings to set.
* @emits change
*/
public set warnings(warnings: typeof this._warnings) {
this._warnings = warnings;
this.emit("change");
}
/**
* Adds one or more warnings to the task.
*
* @param warnings The warnings to add to the task.
* @returns The instance of the task.
* @emits change
*/
public addWarning(...warnings: NonNullable<typeof this._warnings>) {
if (warnings.length) {
if (!this._warnings) this._warnings = [];
this._warnings.push(...warnings);
this.emit("change");
}
return this;
}
// Result
/**
* The result of the task, encapsulated in an Optional object.
*/
protected _result: Optional<TResult> = Optional.empty();
/**
* The result of the task, encapsulated in an Optional object.
*/
public get result() {
return this._result;
}
/**
* Setter for the task's result. Changes the task's status to 'success' and progress to 1.
*
* @param result The result to set for the task.
* @emits progress
* @emits change
*/
protected set result(result: typeof this._result) {
this._result = result;
this._status = "success";
this._progress = 1;
this.emit("progress", this.progress).emit("change");
}
/**
* Sets the result of the task.
*
* @param result The result to set for the task.
* @returns The instance of the task.
*/
protected setResult(result: typeof this._result): this {
this.result = result;
return this;
}
}
/**
* Represents a single task in the task manager system, extending the functionality of `TaskBase`.
* Handles task execution, progress tracking, and status updates.
*
* @template TData - The type of data the task requires.
* @template TResult - The type of result the task produces.
* @template TError - The type of error the task may encounter.
*/
export class Task<TData = any, TResult = any, TError = any> extends TaskBase<TResult, TError> {
/**
* Unique identifier of the task.
*/
readonly id!: string;
/**
* `TaskManager` instance to which this task is bound.
*/
private _manager?: TaskManager;
/**
* Creates an instance of Task.
*
* @param builder - Task builder function used to create new instances of the task.
* @param name - Name of the task.
* @param _config - Configuration object for the task.
* @param data - Data required to execute the task.
*/
constructor(
readonly builder: TaskBuilder<TData, TResult, TError>,
readonly name: string,
private _config: Omit<TaskConfig<TData, TResult, TError>, "name">,
readonly data: TData
) {
super();
this.id = createTaskId(name);
}
/**
* Binds the task to a `TaskManager`.
*
* @param manager - `TaskManager` to bind this task to.
*/
public bind(manager: TaskManager) {
this._manager = manager;
}
/**
* Gets the `TaskManager` to which this task is bound.
*
* @returns The associated TaskManager.
* @throws If the task is not bound to a TaskManager.
*/
public get manager(): TaskManager {
if (!this._manager) {
throw new Error(`Missing TaskManager. Please use ${Task.name}.${this.bind.name}(TaskManager).`);
}
return this._manager;
}
/**
* Executes the task. Changes the task's status and handles the task's result or error.
*
* @returns Result of the task execution.
* @throws If the task is not in the "idle" state or if the task execution fails.
*/
public async execute() {
if (this.status !== "idle") {
throw new Error('Task is not in "idle" state.');
}
this.setStatus("in-progress");
try {
const result = await Promise.resolve(this._config.execute.bind(this)(this.data));
this.result = Optional(result);
} catch (error) {
this.addError(error as TError);
throw error;
}
}
/**
* Parses the task, providing a representation suitable for UI rendering.
*
* @returns The parsed representation of the task.
*/
public parse(): Task.Parsed {
const parsed = this._config.parse?.bind(this)() ?? {};
return {
status: `${this.name} - ${this.status}`,
warnings: this.warnings,
errors: this.errors?.map((error) => {
if (error instanceof Error) {
return error.message;
}
return `${error}`;
}),
result: this.result.isPresent() ? `${this.result.get()}` : undefined,
...parsed,
};
}
/**
* Returns a string representation of the Task instance.
*
* @returns A string representing the task.
*/
public toString() {
return `${Task.name} { name: ${JSON.stringify(this.name)}, id: #${this.id} }`;
}
/**
* Creates a clone of the current task.
*
* @returns A new `Task` instance with the same configuration and data.
*/
public clone() {
return this.builder(this.data);
}
}
/**
* Namespace containing types and interfaces related to `Task`.
*/
export namespace Task {
/**
* Possible statuses of a Task.
*/
export type Status = "idle" | "in-progress" | "error" | "success";
/**
* Interface representing a parsed representation of a Task, suitable for rendering in a UI.
*/
export interface Parsed {
/**
* A node representing the task's status.
*/
status: React.ReactNode;
/**
* Optional array of nodes representing warnings.
*/
warnings?: React.ReactNode[];
/**
* Optional array of nodes representing errors.
*/
errors?: React.ReactNode[];
/**
* Optional node representing the task's result.
*/
result?: React.ReactNode;
}
}
// TODO: Replace `createTaskId` with UUID?
/**
* Creates a unique identifier for a task based on its name and the current time.
* The ID is a combination of a random hex string, the task name in hex, and the current time in hex.
*
* @param name - Name of the task.
* @returns A unique task identifier.
*/
export function createTaskId(name: string) {
const randomHex = Math.floor(Math.random() * Date.now() * name.length)
.toString(16)
.padEnd(12, "0");
const nameHex = [...name].map((c) => c.charCodeAt(0).toString(16)).join("");
const timeHex = Date.now().toString(16).padStart(12, "0");
return `${randomHex}-${nameHex}-${timeHex}`;
}
/**
* Type guard function to check if a given object is an instance of `Task`.
*
* @template TData - The type of data the task requires.
* @template TResult - The type of result the task produces.
* @template TError - The type of error the task may encounter.
* @param task - Object to check.
* @param taskBuilder - Task builder to compare against.
*/
export function isTask<TData, TResult, TError>(
task: unknown,
taskBuilder: TaskBuilder<TData, TResult, TError>
): task is Task<TData, TResult, TError> {
return task instanceof Task && task.builder.id === taskBuilder.id;
}
/**
* Interface for a function that builds `Task` instances.
*
* @template TData - Type of input data for the task.
* @template TResult - Type of result produced by the task.
* @template TError - Type of error that may be thrown by the task.
*/
export interface TaskBuilder<TData, TResult, TError> {
/**
* Function that builds `Task` instances.
*
* @template TData - Type of input data for the task.
* @template TResult - Type of result produced by the task.
* @template TError - Type of error that may be thrown by the task.
* @param data - Data to be passed to the task for execution.
* @return A new `Task` instance.
*/
(data: TData): Task<TData, TResult, TError>;
/**
* Unique identifier of the task builder.
*/
id: string;
/**
* Name of the task.
*/
taskName: string;
}
/**
* Factory function to create a new `TaskBuilder`, which in turn is used to create `Task` instances.
*
* @template TData - The type of data the task requires.
* @template TResult - The type of result the task produces.
* @template TError - The type of error the task may encounter.
* @param config - Configuration object for creating the task.
* @returns A new task builder with the given configuration.
*/
export function createTask<TData = void, TResult = void, TError = Error>({
name,
...config
}: TaskConfig<TData, TResult, TError>): TaskBuilder<TData, TResult, TError> {
const builder: TaskBuilder<TData, TResult, TError> = function (data) {
return new Task<TData, TResult, TError>(builder, name, config, data);
};
builder.id = createTaskId(name);
builder.taskName = name;
builder.toString = function () {
return `TaskBuilder { name: ${JSON.stringify(this.taskName)}, id: #${this.id} }`;
};
return builder;
}
import { EventEmitter } from "./EventEmitter";
import { Task, TaskBuilder, isTask } from "./Task";
/**
* Base class for TaskManager, managing task status, progress, and flags.
* Emits events related to task lifecycle and changes.
*/
class TaskManagerBase extends EventEmitter<{
/**
* Emits on general change.
*/
change: void;
/**
* Emits on new task in progress.
*/
task: Task;
/**
* Emits on task progress change.
*/
progress: number;
/**
* Emits when task fails and `FAIL_ON_ERROR` flag is on.
*/
fail: { task: Task; error: unknown } | unknown;
/**
* Emits when queue was executed successfully.
*/
success: unknown;
}> {
// Status
/**
* Current status.
*/
protected _status: TaskManager.Status = "idle";
/**
* Current status.
*/
public get status() {
return this._status;
}
/**
* Sets the status.
*
* @emits change
*/
protected set status(status: typeof this._status) {
if (status !== this.status) {
this._status = status;
this.emit("change");
}
}
/**
* Sets the status.
*
* @param status - The status to set.
* @returns The instance of the manager.
*/
protected setStatus(status: typeof this._status) {
this.status = status;
return this;
}
/**
* Checks if the status matches any of the provided statuses.
*
* @param statuses - Array of statuses to check against.
* @returns True if the current status is among the provided statuses, false otherwise.
*/
public isStatus(...statuses: Array<typeof this._status>) {
return statuses.includes(this.status);
}
// Progress
/**
* Current progress of tasks ([0, 1]).
*/
protected _progress: number = 0;
/**
* Current progress of tasks ([0, 1]).
*/
public get progress() {
return this._progress;
}
/**
* Sets the progress of tasks.
*
* @emits progress
* @emits change
*/
protected set progress(progress: typeof this._progress) {
const validProgress = progress > 1 ? 1 : progress < 0 ? 0 : progress;
if (validProgress !== this.progress) {
this._progress = validProgress;
this.emit("progress", this.progress).emit("change");
}
}
/**
* Sets the progress of tasks.
*
* @param progress - The progress to set.
* @returns The instance of the manager.
*/
protected setProgress(progress: typeof this._progress) {
this.progress = progress;
return this;
}
// Flags
/**
* Current flags.
*/
protected _flags: Set<TaskManager.Flag> = new Set([TaskManager.Flag.FAIL_ON_ERROR]);
/**
* Gets the current array of flags.
*/
public get flags(): TaskManager.Flag[] {
return Array.from(this._flags);
}
/**
* Sets the flags.
*
* @emits change
*/
protected set flags(flags: typeof this._flags) {
this._flags = flags;
this.emit("change");
}
/**
* Sets the flags.
*
* @param flags - The flags to set.
* @returns The instance of the manager.
*/
protected setFlags(flags: typeof this._flags) {
this.flags = flags;
return this;
}
/**
* Adds a flag.
*
* @param flag - The flag to add.
* @returns The instance of the manager.
*
* @emits change
*/
public addFlag(flag: TaskManager.Flag): this {
this._flags.add(flag);
this.emit("change");
return this;
}
/**
* Removes a flag.
*
* @param flag - The flag to add.
* @returns The instance of the manager.
* @emits change
*/
public removeFlag(flag: TaskManager.Flag): this {
this._flags.delete(flag);
this.emit("change");
return this;
}
/**
* Checks if a given flag is set.
*
* @param flag - The flag to check.
* @returns True if the flag is set, false otherwise.
*/
public hasFlag(flag: TaskManager.Flag): boolean {
return this._flags.has(flag);
}
/**
* Checks if the given flags is set.
*
* @param flags - The flags to check.
* @returns - True if all flags are set, false otherwise.
*/
public hasFlags(...flags: TaskManager.Flag[]): boolean {
return flags.every((flag) => this.hasFlag(flag));
}
// Queue
/**
* Current queue of tasks.
*/
protected _queue: Task[] = [];
/**
* Current queue of tasks.
*/
public get queue() {
return this._queue;
}
/**
* Sets the task queue.
*/
protected set queue(queue: typeof this._queue) {
this._queue = queue;
}
// Tasks
/**
* Current list of executed tasks.
*/
protected _tasks: Task[] = [];
/**
* Current list of executed tasks.
*/
public get tasks() {
return this._tasks;
}
/**
* Sets the list of executed tasks.
*
* @param tasks - The list of tasks to set.
*/
protected set tasks(tasks: typeof this._tasks) {
this._tasks = tasks;
}
}
export class TaskManager extends TaskManagerBase {
/**
* Adds an array of tasks to the task queue.
*
* @param tasks - An array of tasks to add to the queue.
* @returns The instance of the manager.
* @emits change
*/
public addTasks(tasks: Task[]) {
this.queue.push(
...tasks.map((task) => {
task.bind(this);
return task;
})
);
this.emit("change");
return this;
}
/**
* Calculates the overall progress of the tasks.
*
* @returns The calculated progress.
*/
private calculateProgress() {
const tasksProgress = this.tasks.reduce((progress, task) => progress + task.progress, 0);
return tasksProgress / (this.queue.length + this.tasks.length);
}
/**
* Executes tasks in a linear sequence.
*
* @returns A promise that resolves when all tasks in the queue have been executed linearly.
*/
private async executeLinear() {
const task = this.queue.shift();
if (!task) return;
this.tasks.push(task);
this.emit("task", task).emit("change");
task.on("progress", () => {
this.setProgress(this.calculateProgress());
});
return await task.execute();
}
/**
* Executes tasks in parallel.
*
* @returns A promise that resolves when all tasks in the queue have been executed in parallel.
*/
private async executeParallel() {
const queueTasks = [...this.queue];
this.clearQueue();
this.tasks.push(...queueTasks);
const executeTasks = () => {
return queueTasks.map(async (task) => {
this.emit("task", task).emit("change");
task.on("progress", () => {
this.setProgress(this.calculateProgress());
});
try {
await task.execute();
} catch (error: any) {
throw { task, error };
}
});
};
if (this.hasFlag(TaskManager.Flag.FAIL_ON_ERROR)) {
return await Promise.all(executeTasks());
}
return await Promise.allSettled(executeTasks());
}
/**
* Start task manager.
* @param {boolean} [force] - Force start if in "fail" status.
*/
/**
* Starts the execution of tasks in the task manager.
*
* @param force - Force start even if in "fail" status.
* @returns A promise that resolves when task execution starts.
*/
public async start(force?: boolean) {
if (!this.queue.length) {
return console.warn(`${TaskManager.name} empty queue.`);
}
if (!this.isStatus("idle", "stopped") && !(force && this.isStatus("fail"))) {
switch (this.status) {
case "fail":
return console.warn(`${TaskManager.name} failed.`);
case "success":
return console.warn(`${TaskManager.name} succeeded.`);
default:
return console.warn(`${TaskManager.name} is already in progress.`);
}
}
if (this.hasFlag(TaskManager.Flag.STOP)) {
this.removeFlag(TaskManager.Flag.STOP);
}
this.setStatus("in-progress");
while (this.queue.length > 0) {
try {
if (this.hasFlag(TaskManager.Flag.PARALLEL_EXECUTION)) {
await this.executeParallel();
} else {
await this.executeLinear();
}
} catch (error: any) {
if (this.hasFlag(TaskManager.Flag.FAIL_ON_ERROR)) {
return this.setStatus("fail").emit("fail", error);
}
}
if (this.hasFlag(TaskManager.Flag.STOP)) {
return this.removeFlag(TaskManager.Flag.STOP).setStatus("stopped");
}
}
return this.setStatus("success");
}
/**
* Stops the execution of tasks in the task manager.
*/
public stop() {
if (!this.isStatus("in-progress")) {
return console.warn(`${TaskManager.name} is not in-progress.`);
}
this.addFlag(TaskManager.Flag.STOP);
}
/**
* Resets the task manager to its initial state.
*/
public reset() {
if (this.isStatus("in-progress")) {
return console.warn(`${TaskManager.name} is in-progress.`);
}
if (this.isStatus("idle")) {
return console.warn(`${TaskManager.name} is already idle.`);
}
const tmp = [...this.tasks, ...this.queue];
this.queue = [];
this.tasks = [];
this.status = "idle";
this.progress = 0;
this.emit("progress", this.progress);
this.addTasks(tmp.map((task) => task.clone()));
}
/**
* Clears the task queue.
* @returns The instance of the manager.
*/
public clearQueue(): this {
this.queue = [];
this.emit("change");
return this;
}
/**
* Finds a task in the list of tasks.
*
* @param taskBuilder - The builder of the task to find.
* @returns The found task or undefined.
*/
public findTask<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
for (let i = 0; i < this.tasks.length; i++) {
const task: unknown = this.tasks[i];
if (isTask(task, taskBuilder)) {
return task;
}
}
}
/**
* Retrieves a task from the list of tasks.
*
* @param taskBuilder - The builder of the task to retrieve.
* @returns The retrieved task.
* @throws If the task is not found.
*/
public getTask<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
const task = this.findTask(taskBuilder);
if (!task) {
throw new Error(`Task by ${taskBuilder} not found.`);
}
return task;
}
/**
* Retrieves the result of a task.
*
* @param taskBuilder - The builder of the task whose result to retrieve.
* @returns The result of the task.
* @throws If the task is not found or if the result is empty.
*/
public getTaskResult<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
const task = this.getTask(taskBuilder);
if (task.result.isEmpty()) {
throw new Error(`${task} result is empty.`);
}
return task.result.get();
}
/**
* Finds the last task of a specific type in the list of tasks.
*
* @param taskBuilder - The builder of the task to find.
* @returns The found task or undefined.
*/
public findLastTask<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
for (let i = this.tasks.length - 1; i >= 0; i--) {
const task: unknown = this.tasks[i];
if (isTask(task, taskBuilder)) {
return task;
}
}
}
/**
* Retrieves the last task of a specific type from the list of tasks.
*
* @param taskBuilder - The builder of the task to retrieve.
* @returns The retrieved task.
* @throws If the task is not found.
*/
public getLastTask<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
const task = this.findLastTask(taskBuilder);
if (!task) {
throw new Error(`Task by ${taskBuilder} not found.`);
}
return task;
}
/**
* Retrieves the result of the last task of a specific type.
*
* @param taskBuilder - The builder of the task whose result to retrieve.
* @returns The result of the task.
* @throws If the task is not found or if the result is empty.
*/
public getLastTaskResult<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
const task = this.getLastTask(taskBuilder);
if (task.result.isEmpty()) {
throw new Error(`${task} result is empty.`);
}
return task.result.get();
}
/**
* Finds all tasks of a specific type in the list of tasks.
*
* @param taskBuilder - The builder of the task type to find.
* @returns Array of found tasks.
*/
public findTasks<TData, TResult, TError>(taskBuilder: TaskBuilder<TData, TResult, TError>) {
return this.tasks.filter((task) => isTask(task, taskBuilder)) as Task<TData, TResult, TError>[];
}
/**
* Retrieves the results of all tasks of a specific type.
*
* @param taskBuilder - The builder of the task type whose results to retrieve.
* @returns Array of results.
* @throws If any of the task results is empty.
*/
public getTasksResults<TData, TResult, TError>(
taskBuilder: TaskBuilder<TData, TResult, TError>
): NonNullable<TResult>[] {
const results: NonNullable<TResult>[] = [];
for (let i = 0; i < this.tasks.length; i++) {
const task: unknown = this.tasks[i];
if (isTask(task, taskBuilder)) {
if (task.result.isEmpty()) {
throw new Error(`${task} result is empty.`);
}
results.push(task.result.get());
}
}
return results;
}
}
/**
* Namespace for `TaskManager` related types and enumerations.
*/
export namespace TaskManager {
/**
* Possible statuses of a `TaskManager`.
*/
export type Status = "idle" | "in-progress" | "fail" | "success" | "stopped";
/**
* Various flags that can be used to control the behavior of the TaskManager.
*/
export enum Flag {
/**
* Flag indicating that the execution loop should stop.
*/
STOP = "STOP",
/**
* Flag indicating that the execution should stop if any task fails.
*/
FAIL_ON_ERROR = "FAIL_ON_ERROR",
/**
* Flag for enabling parallel execution of tasks.
*/
PARALLEL_EXECUTION = "PARALLEL_EXECUTION",
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment