{"id":21259474,"url":"https://github.com/ori88c/delayed-async-task","last_synced_at":"2026-01-25T07:02:14.127Z","repository":{"id":246792221,"uuid":"822273815","full_name":"ori88c/delayed-async-task","owner":"ori88c","description":"A modern `setTimeout` substitute tailored for asynchronous tasks, designed to schedule a single delayed execution. Features status getters to communicate the execution status, the ability to abort a pending execution, and the option to gracefully await the completion of an ongoing execution.","archived":false,"fork":false,"pushed_at":"2024-10-09T20:05:01.000Z","size":194,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-30T17:53:02.111Z","etag":null,"topics":["async","dangling-promise","deferred-promise","deferred-task","delayed-job","delayed-jobs","delayed-task","executor","job-runner","job-scheduler","npm","npm-package","one-time","promise","settimeout","single-execution","task","task-executor","task-runner","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ori88c.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-30T19:19:04.000Z","updated_at":"2024-10-09T19:59:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"9a19a1a4-6e7f-40c2-a4ad-a63f6d254c96","html_url":"https://github.com/ori88c/delayed-async-task","commit_stats":null,"previous_names":["ori88c/delayed-async-task"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ori88c/delayed-async-task","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ori88c%2Fdelayed-async-task","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ori88c%2Fdelayed-async-task/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ori88c%2Fdelayed-async-task/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ori88c%2Fdelayed-async-task/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ori88c","download_url":"https://codeload.github.com/ori88c/delayed-async-task/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ori88c%2Fdelayed-async-task/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28747308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T05:12:38.112Z","status":"ssl_error","status_checked_at":"2026-01-25T05:04:50.338Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","dangling-promise","deferred-promise","deferred-task","delayed-job","delayed-jobs","delayed-task","executor","job-runner","job-scheduler","npm","npm-package","one-time","promise","settimeout","single-execution","task","task-executor","task-runner","typescript"],"created_at":"2024-11-21T04:14:12.668Z","updated_at":"2026-01-25T07:02:14.106Z","avatar_url":"https://github.com/ori88c.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch2 align=\"middle\"\u003eDelayed Async Task\u003c/h2\u003e\n\nThe `DelayedAsyncTask` class provides a modern substitute for JavaScript's built-in `setTimeout` function, specifically tailored for asynchronous tasks (callbacks returning a Promise). This **one-time** scheduler is designed to handle the delayed execution of a single asynchronous task, offering advanced capabilities beyond basic delay.\n\nKey features include:\n\n* __Status Communication__: Easily check the current status of the scheduled task.\n* __Graceful Termination__: Await the completion of an ongoing execution, ensuring deterministic termination when needed.\n\nThis class is ideal for scenarios where precise control over the execution and termination of asynchronous tasks is required. If you need to manage **multiple** asynchronous tasks, potentially in large numbers, consider the [delayed-async-tasks-manager](https://www.npmjs.com/package/delayed-async-tasks-manager) package, which extends the functionality of this package.\n\n## Table of Contents\n\n* [Key Features](#key-features)\n* [API](#api)\n* [Execution Status Getters](#execution-status-getters)\n* [Graceful and Deterministic Termination](#graceful-termination)\n* [Zero Over-Engineering, No External Dependencies](#no-external-dependencies)\n* [Non-Persistent Scheduling](#non-persistent)\n* [Error Handling](#error-handling)\n* [Use-case Example](#use-case-example)\n* [Breaking Change in Version 2.0.0](#breaking-change-2)\n* [License](#license)\n\n## Key Features :sparkles:\u003ca id=\"key-features\"\u003e\u003c/a\u003e\n\n* __Modern Substitute for Javascript's 'setTimeout'__: Specifically designed for scheduling asynchronous tasks.\n* __Execution Status Getters :bar_chart:__: Allows users to check the task's execution status, helping to prevent potential race conditions.\n* __Graceful and Deterministic Termination :hourglass:__: The `awaitCompletionIfCurrentlyExecuting` method resolves once the currently executing task finishes, or resolves immediately if the task is not executing.\n* __Robust Error Handling :warning:__: If the task throws an uncaught error, the error is captured and accessible via the `uncaughtRejection` getter.\n* __Comprehensive Documentation :books:__: The class is thoroughly documented, enabling IDEs to provide helpful tooltips that enhance the coding experience.\n* __Fully Tested :test_tube:__: Extensively covered by unit tests.\n* __No External Runtime Dependencies__: Lightweight component, only development dependencies are used.\n* Non-Durable Scheduling: Scheduling stops if the application crashes or goes down.\n* ES2020 Compatibility.\n* TypeScript support.\n\n## API :globe_with_meridians:\u003ca id=\"api\"\u003e\u003c/a\u003e\n\nThe `DelayedAsyncTask` class provides the following methods:\n\n* __tryAbort__: Attempts to abort a pending task execution, if one exists.\n* __awaitCompletionIfCurrentlyExecuting__: This method resolves once the currently executing task completes, or resolves immediately if the task is not currently in-progress.\n\nIf needed, refer to the code documentation for a more comprehensive description of each method.\n\n## Execution Status Getters :mag:\u003ca id=\"execution-status-getters\"\u003e\u003c/a\u003e\n\nThe `DelayedAsyncTask` class provides five getter methods to communicate the task's status to users:\n\n* `isPending`: Indicates that the execution has not started yet.\n* `isAborted`: Indicates that the task was aborted by the `tryAbort` method.\n* `isExecuting`: Indicates that the task is currently executing.\n* `isCompleted`: Indicates that the task has completed.\n* `isUncaughtRejectionOccurred`: Indicates that the task threw an uncaught error. The error can be accessed using the `uncaughtRejection` getter.\n\n## Graceful and Deterministic Termination :hourglass:\u003ca id=\"graceful-termination\"\u003e\u003c/a\u003e\n\nIn the context of asynchronous tasks and schedulers, graceful and deterministic termination is **often overlooked**. `DelayedAsyncTask` provides an out-of-the-box mechanism to await the completion of an asynchronous task that has already started but not yet finished, via the `awaitCompletionIfCurrentlyExecuting` method.\n\nWithout deterministic termination, leftover references from incomplete executions can cause issues such as unexpected behavior during unit tests. A clean state is essential for each test, as ongoing tasks from a previous test can interfere with subsequent ones.\n\nThis feature is crucial whenever your component has a `stop` or `terminate` method. Consider the following example:\n```ts\nclass Component {\n  private _timeout: NodeJS.Timeout;\n\n  public start(): void {\n    this._timeout = setTimeout(this._prolongedTask.bind(this), 8000);\n  }\n\n  public async stop(): Promise\u003cvoid\u003e {\n    if (this._timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = undefined;\n      // The dangling promise of _prolongedTask might still be running in the\n      // background, leading to non-deterministic termination and potential\n      // race conditions or unexpected behavior.\n    }\n  }\n\n  private async _prolongedTask(): Promise\u003cvoid\u003e {\n    // Perform your task here.\n  }\n}\n```\nWhile it is possible to manually address this issue by **avoiding dangling promises** and introducing more state properties, doing so can compromise the **Single Responsibility Principle** of your component. It can also decrease readability and likely introduce code duplication, as this need is frequent.  \nThe above example can be fixed using the `DelayedAsyncTask` class as follows:\n```ts\nimport { DelayedAsyncTask } from 'delayed-async-task';\n\nclass Component {\n  private readonly _delayedTask: AsyncDelayedTask;\n\n  public start(): void {\n    this._delayedTask = new AsyncDelayedTask(\n      this._prolongedTask.bind(this),\n      8000\n    );\n  }\n\n  public async stop(): Promise\u003cvoid\u003e {\n    if (!this._delayedTask.tryAbort()) {\n      await this._delayedTask.awaitCompletionIfCurrentlyExecuting();\n    }\n  }\n\n  private async _prolongedTask(): Promise\u003cvoid\u003e {\n    // Perform your task here.\n  }\n}\n```\n\nAnother scenario where this feature is highly recommended is when a schedule might be aborted, such as in an abort-and-reschedule situation. If the task is currently executing, you may not be able to abort it. In such cases, you can ignore the reschedule request, await the current execution to complete, or implement any other business logic that suits your requirements.\n\n## Zero Over-Engineering, No External Dependencies\u003ca id=\"no-external-dependencies\"\u003e\u003c/a\u003e\n\nThis component provides a lightweight, dependency-free solution. It is designed to be simple and efficient, ensuring minimal overhead. Additionally, it can serve as a building block for more advanced implementations, such as a Delayed Async Tasks Manager, if needed.\n\n## Non-Persistent Scheduling\u003ca id=\"non-persistent\"\u003e\u003c/a\u003e\n\nThis component features non-durable scheduling, which means that if the app crashes or goes down, scheduling stops.\n\nIf you need to guarantee durability over a multi-node deployment, consider using this scheduler as a building block or use other custom-made solutions for that purpose.\n\n## Error Handling :warning:\u003ca id=\"error-handling\"\u003e\u003c/a\u003e\n\nUnlike `setTimeout` in Node.js, where errors from rejected promises propagate to the event loop and trigger an `uncaughtRejection` event, this package offers robust error handling:\n\n* Errors thrown during task's execution are captured, and accessible via the `uncaughtRejection` getter.\n* Use the `isUncaughtRejectionOccurred` getter to determine if an uncaught error occurred during execution.\n\nIdeally, a delayed task should handle its own errors and **avoid** throwing uncaught exceptions.\n\n## Use-case Example :man_technologist:\u003ca id=\"use-case-example\"\u003e\u003c/a\u003e\n\nConsider a Background Updates Manager. For simplicity, we assume that updates occur only following an on-demand request by the admin, triggered by the execution of the `scheduleNextJob` method. Additionally, assume that only one future background update task can be scheduled at any given time. This means that scheduling a new task will abort any previously scheduled task that has not yet started.\n\nPlease note that this example is overly simplified. Real-world usage examples can be more complex, often involving durability and synchronization with external resources, as most modern applications are stateless.\n\n```ts\nimport { DelayedAsyncTask } from 'delayed-async-task';\n\nclass BackgroundUpdatesManager {\n  private _delayedBackgroundTask: DelayedAsyncTask?;\n\n  public async scheduleNextJob(nextExecutionDate: Date): Promise\u003cvoid\u003e {\n    // This method may override a previous schduled date, thus we need to abort\n    // such a previous-schedule if possible.\n    await this._abortIfPossibleOrAwaitCompletion();\n\n    const msTillNextExecution = nextExecutionDate.getTime() - Date.now();\n    this._delayedBackgroundTask = new DelayedAsyncTask(\n      this._installNewestUpdates.bind(this),\n      msTillNextExecution\n    );\n  }\n\n  private async _abortIfPossibleOrAwaitCompletion(): Promise\u003cvoid\u003e {\n    if (!this._delayedBackgroundTask) {\n      return; // No previous execution exists.\n    }\n\n    if (this._delayedBackgroundTask.tryAbort()) {\n      logger.info(`Aborted a previously scheduled background updates task`);\n      return;\n    }\n        \n    if (this._delayedBackgroundTask.isExecuting) {\n      logger.info('Waiting for a previously scheduled background updates task to finish execution...');\n      await this._delayedBackgroundTask.awaitCompletionIfCurrentlyExecuting();\n    }\n\n    const uncaughtError = this._delayedBackgroundTask.uncaughtRejection;\n    if (uncaughtError) {\n      logger.error(\n        `Previous background updates execution failed with uncaught error: ${uncaughtError.message}`\n      );\n      return;\n    }\n\n    // At this stage, necessarily this._delayedBackgroundTask.isCompleted === true\n    // as all other options were eliminated.\n    logger.info('Previously scheduled background updates task has finished successfully');\n  }\n    \n  private async _installNewestUpdates(): Promise\u003cvoid\u003e {\n    // Potentially a prolonged operation:\n    // * Checks for the newest-uninstalled updates.\n    // * Downloads them.\n    // * Installs them.\n  }\n}\n```\n\n## Breaking Change in Version 2.0.0 :boom:\u003ca id=\"breaking-change-2\"\u003e\u003c/a\u003e\n\nIn version 2.0.0, the target compatibility has been upgraded from ES6 to ES2020. This change was made to leverage the widespread adoption of ES2020, in particular its native support for async/await.\n\n## License :scroll:\u003ca id=\"license\"\u003e\u003c/a\u003e\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fori88c%2Fdelayed-async-task","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fori88c%2Fdelayed-async-task","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fori88c%2Fdelayed-async-task/lists"}