From 752d25bb66b47abedb7e07974ce0a141f6d1f7bf Mon Sep 17 00:00:00 2001 From: Ori Cohen Date: Sun, 22 Sep 2024 20:29:06 +0300 Subject: [PATCH] Bump version to v2.0.1: README, Unit Tests and implementation refinements --- README.md | 25 +- dist/delayed-async-task.d.ts | 12 +- dist/delayed-async-task.js | 8 +- dist/delayed-async-task.test.js | 74 +++--- dist/delayed-async-task.test.js.map | 2 +- package-lock.json | 4 +- package.json | 2 +- src/delayed-async-task.test.ts | 353 ++++++++++++++-------------- src/delayed-async-task.ts | 14 +- 9 files changed, 255 insertions(+), 239 deletions(-) diff --git a/README.md b/README.md index a298308..ebf7a5e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The `DelayedAsyncTask` class provides a modern substitute for JavaScript's built Key features include: * __Status Communication__: Easily check the current status of the scheduled task. -* __Graceful Await__: Await the completion of an ongoing execution, ensuring deterministic termination when needed. +* __Graceful Termination__: Await the completion of an ongoing execution, ensuring deterministic termination when needed. This class is ideal for scenarios where precise control over the execution and termination of asynchronous tasks is required. @@ -13,15 +13,24 @@ This class is ideal for scenarios where precise control over the execution and t * __Modern Substitute for Javascript's 'setTimeout'__: Specifically designed for scheduling asynchronous tasks. * __Execution Status Getters__: Allows users to check the task's execution status, helping to prevent potential race conditions. -* __Graceful and Deterministic Termination__: The `awaitCompletionIfCurrentlyExecuting` method resolves once the currently executing task finishes or resolves immediately if the task is not executing. +* __Graceful and Deterministic Termination__: The `awaitCompletionIfCurrentlyExecuting` method resolves once the currently executing task finishes, or resolves immediately if the task is not executing. * __Robust Error Handling__: If the task throws an uncaught error, the error is captured and accessible via the `uncaughtRejection` getter. -* __Comprehensive Documentation__: The class is thoroughly documented, enabling IDEs to provide helpful tooltips that enhance the coding experience. -* __Fully Tested__: Covered extensively by unit tests. +* __Comprehensive Documentation :books:__: The class is thoroughly documented, enabling IDEs to provide helpful tooltips that enhance the coding experience. +* __Fully Tested :test_tube:__: Extensively covered by unit tests. * __No External Runtime Dependencies__: Lightweight component, only development dependencies are used. * Non-Durable Scheduling: Scheduling stops if the application crashes or goes down. -* ES6 Compatibility. +* ES2020 Compatibility. * TypeScript support. +## API + +The `DelayedAsyncTask` class provides the following methods: + +* __tryAbort__: Attempts to abort a pending task execution, if one exists. +* __awaitCompletionIfCurrentlyExecuting__: This method resolves once the currently executing task completes, or resolves immediately if the task is not currently in-progress. + +If needed, refer to the code documentation for a more comprehensive description of each method. + ## Execution Status Getters :rocket: The `DelayedAsyncTask` class provides five getter methods to communicate the task's status to users: @@ -41,7 +50,7 @@ Without deterministic termination, leftover references from incomplete execution This feature is crucial whenever your component has a `stop` or `terminate` method. Consider the following example: ```ts class Component { - private readonly _timeout: NodeJS.Timeout?; + private _timeout: NodeJS.Timeout; public start(): void { this._timeout = setTimeout(this._prolongedTask.bind(this), 8000); @@ -50,7 +59,7 @@ class Component { public async stop(): Promise { if (this._timeout) { clearTimeout(this._timeout); - this._timeout = null; + this._timeout = undefined; // The dangling promise of _prolongedTask might still be running in the // background, leading to non-deterministic termination and potential // race conditions or unexpected behavior. @@ -62,7 +71,7 @@ class Component { } } ``` -While 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. +While 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. The above example can be fixed using the `DelayedAsyncTask` class as follows: ```ts import { DelayedAsyncTask } from 'delayed-async-task'; diff --git a/dist/delayed-async-task.d.ts b/dist/delayed-async-task.d.ts index 075615d..6d0684a 100644 --- a/dist/delayed-async-task.d.ts +++ b/dist/delayed-async-task.d.ts @@ -28,9 +28,9 @@ */ export declare class DelayedAsyncTask { private _status; - private _timeout?; - private _uncaughtRejection?; - private _currentlyExecutingTaskPromise?; + private _timeout; + private _uncaughtRejection; + private _currentlyExecutingTaskPromise; private readonly _task; /** * constructor @@ -38,9 +38,9 @@ export declare class DelayedAsyncTask { * Schedules the provided task to start execution after the specified delay. * * @param task The async function to execute. - * @param msDelayTillExecution The delay in milliseconds before the task starts execution. + * @param delayTillExecutionMs The delay in milliseconds before the task starts execution. */ - constructor(task: () => Promise, msDelayTillExecution: number); + constructor(task: () => Promise, delayTillExecutionMs: number); /** * isPending * @@ -96,7 +96,7 @@ export declare class DelayedAsyncTask { /** * awaitCompletionIfCurrentlyExecuting * - * This method resolves once the currently executing task finishes, or resolves immediately if the + * This method resolves once the currently executing task completes, or resolves immediately if the * task is not currently in-progress. * * This capability addresses the need for graceful and deterministic termination: diff --git a/dist/delayed-async-task.js b/dist/delayed-async-task.js index 075e636..b60af67 100644 --- a/dist/delayed-async-task.js +++ b/dist/delayed-async-task.js @@ -36,9 +36,9 @@ class DelayedAsyncTask { * Schedules the provided task to start execution after the specified delay. * * @param task The async function to execute. - * @param msDelayTillExecution The delay in milliseconds before the task starts execution. + * @param delayTillExecutionMs The delay in milliseconds before the task starts execution. */ - constructor(task, msDelayTillExecution) { + constructor(task, delayTillExecutionMs) { this._status = "PENDING"; this._task = task; // The `setTimeout` callback is deliberately non-async, to prevent a dangling promise. @@ -47,7 +47,7 @@ class DelayedAsyncTask { this._timeout = undefined; this._status = "EXECUTING"; this._currentlyExecutingTaskPromise = this._handleTaskExecution(); - }, msDelayTillExecution); + }, delayTillExecutionMs); } /** * isPending @@ -124,7 +124,7 @@ class DelayedAsyncTask { /** * awaitCompletionIfCurrentlyExecuting * - * This method resolves once the currently executing task finishes, or resolves immediately if the + * This method resolves once the currently executing task completes, or resolves immediately if the * task is not currently in-progress. * * This capability addresses the need for graceful and deterministic termination: diff --git a/dist/delayed-async-task.test.js b/dist/delayed-async-task.test.js index 64b7a23..1110c51 100644 --- a/dist/delayed-async-task.test.js +++ b/dist/delayed-async-task.test.js @@ -20,23 +20,24 @@ describe('DelayedAsyncTask tests', () => { jest.useRealTimers(); }); describe('Happy path tests', () => { - test('should indicate correct state when task executes successfully', async () => { - let taskCompletedSuccessfully = false; + test('should reflect correct state after successful task execution', async () => { + let isCompleted = false; let completeTask; + // We create unresolved promises, simulating an async work in progress. + // They will be resolved later, once we want to simulate a successful completion + // of the async work. const task = async () => new Promise(res => { completeTask = () => { res(); - taskCompletedSuccessfully = true; + isCompleted = true; }; - // The task returns a promise in pending-state. It will be fulfilled - // only by manually invoking `completeTask`. }); expect(setTimeoutSpy).toHaveBeenCalledTimes(0); const delayedTask = new delayed_async_task_1.DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. - expect(taskCompletedSuccessfully).toBe(false); + expect(isCompleted).toBe(false); expect(delayedTask.isPending).toBe(true); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isExecuting).toBe(false); expect(delayedTask.isCompleted).toBe(false); @@ -44,47 +45,49 @@ describe('DelayedAsyncTask tests', () => { expect(delayedTask.uncaughtRejection).toBe(undefined); // The time has come, `setTimeout` will trigger the execution. jest.runOnlyPendingTimers(); - // Trigger an event loop. Only `resolveFast` will be resolved as we haven't - // decided to complete the task yet. + // Trigger an event loop. Among the provided promises, only `resolveFast` + // will resolve, since the task hasn't been completed yet. const awaitCompletionPromise = delayedTask.awaitCompletionIfCurrentlyExecuting(); await Promise.race([ awaitCompletionPromise, resolveFast() ]); - // Execution indicator should be on. expect(delayedTask.isExecuting).toBe(true); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isPending).toBe(false); expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isCompleted).toBe(false); expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); expect(delayedTask.uncaughtRejection).toBe(undefined); - expect(taskCompletedSuccessfully).toBe(false); + expect(isCompleted).toBe(false); // Now, we simulate the task's completion (its promise will be fulfilled). completeTask(); await awaitCompletionPromise; - expect(taskCompletedSuccessfully).toBe(true); + expect(isCompleted).toBe(true); expect(delayedTask.isCompleted).toBe(true); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isPending).toBe(false); expect(delayedTask.isExecuting).toBe(false); expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); expect(delayedTask.uncaughtRejection).toBe(undefined); - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. expect(setTimeoutSpy).toHaveBeenCalledTimes(1); }); }); describe('Negative path tests', () => { - test('should successfully abort execution when the abort attempt precedes the scheduled time', async () => { + test('should successfully abort execution when attempted before the scheduled time', async () => { let didTaskExecute = false; - const task = async () => { didTaskExecute = true; }; + const task = () => new Promise(res => { + didTaskExecute = true; + res(); + }); expect(setTimeoutSpy).toHaveBeenCalledTimes(0); const delayedTask = new delayed_async_task_1.DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. expect(delayedTask.isPending).toBe(true); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isExecuting).toBe(false); expect(delayedTask.isCompleted).toBe(false); @@ -93,7 +96,7 @@ describe('DelayedAsyncTask tests', () => { expect(delayedTask.tryAbort()).toBe(true); // No pending timer should exist, thus nothing should happen: jest.runOnlyPendingTimers(); - // Should resolve immediately as no task is executing: + // Should resolve immediately since no task is executing: await delayedTask.awaitCompletionIfCurrentlyExecuting(); expect(didTaskExecute).toBe(false); expect(delayedTask.isAborted).toBe(true); @@ -103,24 +106,25 @@ describe('DelayedAsyncTask tests', () => { expect(delayedTask.isCompleted).toBe(false); expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); expect(delayedTask.uncaughtRejection).toBe(undefined); - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. expect(setTimeoutSpy).toHaveBeenCalledTimes(1); }); - test('should fail aborting task when execution is already ongoing', async () => { + test('should fail to abort the task when execution is already ongoing', async () => { let completeTask; + // We create unresolved promises, simulating an async work in progress. + // They will be resolved later, once we want to simulate a successful completion + // of the async work. const task = async () => new Promise(res => { completeTask = res; - // The task returns a promise in pending-state. It will be fulfilled - // only by manually invoking `completeTask`. }); expect(setTimeoutSpy).toHaveBeenCalledTimes(0); const delayedTask = new delayed_async_task_1.DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. // The time has come, `setTimeout` will trigger the task's execution. jest.runOnlyPendingTimers(); - // Trigger an event loop, only `resolveFast` will resolved as we haven't - // decided to complete the task yet. + // Trigger an event loop. Among the provided promises, only `resolveFast` + // will resolve, since the task hasn't been completed yet. const awaitCompletionPromise = delayedTask.awaitCompletionIfCurrentlyExecuting(); await Promise.race([ awaitCompletionPromise, @@ -131,24 +135,24 @@ describe('DelayedAsyncTask tests', () => { completeTask(); await awaitCompletionPromise; expect(delayedTask.isCompleted).toBe(true); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isPending).toBe(false); expect(delayedTask.isExecuting).toBe(false); expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); expect(delayedTask.uncaughtRejection).toBe(undefined); - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. expect(setTimeoutSpy).toHaveBeenCalledTimes(1); }); - test('should capture uncaught exception when thrown during execution', async () => { + test('should capture uncaught exceptions thrown during execution', async () => { const error = new Error("בוקה ומבוקה! ולב נמס! ופק ברכיים"); const task = async () => { throw error; }; expect(setTimeoutSpy).toHaveBeenCalledTimes(0); const delayedTask = new delayed_async_task_1.DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. expect(delayedTask.isPending).toBe(true); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isExecuting).toBe(false); expect(delayedTask.isCompleted).toBe(false); @@ -158,13 +162,13 @@ describe('DelayedAsyncTask tests', () => { await delayedTask.awaitCompletionIfCurrentlyExecuting(); expect(delayedTask.isUncaughtRejectionOccurred).toBe(true); expect(delayedTask.uncaughtRejection).toBe(error); - // All other getters should be false. + // All other state getters should return false. expect(delayedTask.isPending).toBe(false); expect(delayedTask.isAborted).toBe(false); expect(delayedTask.isExecuting).toBe(false); expect(delayedTask.isCompleted).toBe(false); - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. expect(setTimeoutSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/dist/delayed-async-task.test.js.map b/dist/delayed-async-task.test.js.map index 828e90a..d8c3465 100644 --- a/dist/delayed-async-task.test.js.map +++ b/dist/delayed-async-task.test.js.map @@ -1 +1 @@ -{"version":3,"file":"delayed-async-task.test.js","sourceRoot":"","sources":["../src/delayed-async-task.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"delayed-async-task.test.js","sourceRoot":"","sources":["../src/delayed-async-task.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b5a3675..191ee01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "delayed-async-task", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "delayed-async-task", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.12", diff --git a/package.json b/package.json index f2faa4b..eb32628 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "delayed-async-task", - "version": "2.0.0", + "version": "2.0.1", "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.", "repository": { "type": "git", diff --git a/src/delayed-async-task.test.ts b/src/delayed-async-task.test.ts index 9056e26..d141c80 100644 --- a/src/delayed-async-task.test.ts +++ b/src/delayed-async-task.test.ts @@ -13,194 +13,197 @@ const resolveFast = async () => { expect(14).toBeGreaterThan(3); }; const MOCK_MS_DELAY_TILL_EXECUTION = 83564; describe('DelayedAsyncTask tests', () => { - let setTimeoutSpy: jest.SpyInstance; + let setTimeoutSpy: jest.SpyInstance; - beforeEach(() => { - jest.useFakeTimers(); - setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - }); + beforeEach(() => { + jest.useFakeTimers(); + setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + }); - afterEach(() => { - jest.restoreAllMocks(); - jest.useRealTimers(); - }); + afterEach(() => { + jest.restoreAllMocks(); + jest.useRealTimers(); + }); - describe('Happy path tests', () => { - test('should indicate correct state when task executes successfully', async () => { - let taskCompletedSuccessfully = false; - let completeTask: () => void; - const task = async (): Promise => new Promise(res => { - completeTask = (): void => { - res(); - taskCompletedSuccessfully = true; - }; - // The task returns a promise in pending-state. It will be fulfilled - // only by manually invoking `completeTask`. - }); + describe('Happy path tests', () => { + test('should reflect correct state after successful task execution', async () => { + let isCompleted = false; + let completeTask: () => void; + // We create unresolved promises, simulating an async work in progress. + // They will be resolved later, once we want to simulate a successful completion + // of the async work. + const task = async (): Promise => new Promise(res => { + completeTask = (): void => { + res(); + isCompleted = true; + }; + }); - expect(setTimeoutSpy).toHaveBeenCalledTimes(0); - const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. - expect(taskCompletedSuccessfully).toBe(false); - - expect(delayedTask.isPending).toBe(true); - // All other getters should be false. - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isCompleted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); - - // The time has come, `setTimeout` will trigger the execution. - jest.runOnlyPendingTimers(); - // Trigger an event loop. Only `resolveFast` will be resolved as we haven't - // decided to complete the task yet. - const awaitCompletionPromise = delayedTask.awaitCompletionIfCurrentlyExecuting(); - await Promise.race([ - awaitCompletionPromise, - resolveFast() - ]); - - // Execution indicator should be on. - expect(delayedTask.isExecuting).toBe(true); - // All other getters should be false. - expect(delayedTask.isPending).toBe(false); - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isCompleted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); - expect(taskCompletedSuccessfully).toBe(false); - - // Now, we simulate the task's completion (its promise will be fulfilled). - completeTask(); - await awaitCompletionPromise; - expect(taskCompletedSuccessfully).toBe(true); - - expect(delayedTask.isCompleted).toBe(true); - // All other getters should be false. - expect(delayedTask.isPending).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); + expect(setTimeoutSpy).toHaveBeenCalledTimes(0); + const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. + expect(isCompleted).toBe(false); + + expect(delayedTask.isPending).toBe(true); + // All other state getters should return false. + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isCompleted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); + + // The time has come, `setTimeout` will trigger the execution. + jest.runOnlyPendingTimers(); + // Trigger an event loop. Among the provided promises, only `resolveFast` + // will resolve, since the task hasn't been completed yet. + const awaitCompletionPromise = delayedTask.awaitCompletionIfCurrentlyExecuting(); + await Promise.race([ + awaitCompletionPromise, + resolveFast() + ]); + + expect(delayedTask.isExecuting).toBe(true); + // All other state getters should return false. + expect(delayedTask.isPending).toBe(false); + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isCompleted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); + expect(isCompleted).toBe(false); + + // Now, we simulate the task's completion (its promise will be fulfilled). + completeTask(); + await awaitCompletionPromise; + expect(isCompleted).toBe(true); + + expect(delayedTask.isCompleted).toBe(true); + // All other state getters should return false. + expect(delayedTask.isPending).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); - }); + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); }); + }); - describe('Negative path tests', () => { - test('should successfully abort execution when the abort attempt precedes the scheduled time', async () => { - let didTaskExecute = false; - const task = async (): Promise => { didTaskExecute = true; }; + describe('Negative path tests', () => { + test('should successfully abort execution when attempted before the scheduled time', async () => { + let didTaskExecute = false; + const task = (): Promise => new Promise(res => { + didTaskExecute = true; + res(); + }); - expect(setTimeoutSpy).toHaveBeenCalledTimes(0); - const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. + expect(setTimeoutSpy).toHaveBeenCalledTimes(0); + const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. - expect(delayedTask.isPending).toBe(true); - // All other getters should be false. - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isCompleted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); + expect(delayedTask.isPending).toBe(true); + // All other state getters should return false. + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isCompleted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); - expect(delayedTask.tryAbort()).toBe(true); + expect(delayedTask.tryAbort()).toBe(true); - // No pending timer should exist, thus nothing should happen: - jest.runOnlyPendingTimers(); - // Should resolve immediately as no task is executing: - await delayedTask.awaitCompletionIfCurrentlyExecuting(); - - expect(didTaskExecute).toBe(false); - - expect(delayedTask.isAborted).toBe(true); - // All other getters should be false. - expect(delayedTask.isPending).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isCompleted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); - - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); - }); - - test('should fail aborting task when execution is already ongoing', async () => { - let completeTask: PromiseResolveCallbackType; - const task = async (): Promise => new Promise(res => { - completeTask = res; - // The task returns a promise in pending-state. It will be fulfilled - // only by manually invoking `completeTask`. - }); + // No pending timer should exist, thus nothing should happen: + jest.runOnlyPendingTimers(); + // Should resolve immediately since no task is executing: + await delayedTask.awaitCompletionIfCurrentlyExecuting(); - expect(setTimeoutSpy).toHaveBeenCalledTimes(0); - const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. - - // The time has come, `setTimeout` will trigger the task's execution. - jest.runOnlyPendingTimers(); - // Trigger an event loop, only `resolveFast` will resolved as we haven't - // decided to complete the task yet. - const awaitCompletionPromise = delayedTask.awaitCompletionIfCurrentlyExecuting(); - await Promise.race([ - awaitCompletionPromise, - resolveFast() - ]); + expect(didTaskExecute).toBe(false); + + expect(delayedTask.isAborted).toBe(true); + // All other getters should be false. + expect(delayedTask.isPending).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isCompleted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); + + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); + }); + + test('should fail to abort the task when execution is already ongoing', async () => { + let completeTask: PromiseResolveCallbackType; + // We create unresolved promises, simulating an async work in progress. + // They will be resolved later, once we want to simulate a successful completion + // of the async work. + const task = async (): Promise => new Promise(res => { + completeTask = res; + }); + + expect(setTimeoutSpy).toHaveBeenCalledTimes(0); + const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. + + // The time has come, `setTimeout` will trigger the task's execution. + jest.runOnlyPendingTimers(); + // Trigger an event loop. Among the provided promises, only `resolveFast` + // will resolve, since the task hasn't been completed yet. + const awaitCompletionPromise = delayedTask.awaitCompletionIfCurrentlyExecuting(); + await Promise.race([ + awaitCompletionPromise, + resolveFast() + ]); - // Cannot abort a task which already began execution. - expect(delayedTask.tryAbort()).toBe(false); - - completeTask(); - await awaitCompletionPromise; - - expect(delayedTask.isCompleted).toBe(true); - // All other getters should be false. - expect(delayedTask.isPending).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); + // Cannot abort a task which already began execution. + expect(delayedTask.tryAbort()).toBe(false); + + completeTask(); + await awaitCompletionPromise; + + expect(delayedTask.isCompleted).toBe(true); + // All other state getters should return false. + expect(delayedTask.isPending).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); - }); - - test('should capture uncaught exception when thrown during execution', async () => { - const error = new Error("בוקה ומבוקה! ולב נמס! ופק ברכיים"); - const task = async (): Promise => { throw error; }; - - expect(setTimeoutSpy).toHaveBeenCalledTimes(0); - const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. - - expect(delayedTask.isPending).toBe(true); - // All other getters should be false. - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isCompleted).toBe(false); - expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); - expect(delayedTask.uncaughtRejection).toBe(undefined); - - jest.runOnlyPendingTimers(); - await delayedTask.awaitCompletionIfCurrentlyExecuting(); - - expect(delayedTask.isUncaughtRejectionOccurred).toBe(true); - expect(delayedTask.uncaughtRejection).toBe(error); - // All other getters should be false. - expect(delayedTask.isPending).toBe(false); - expect(delayedTask.isAborted).toBe(false); - expect(delayedTask.isExecuting).toBe(false); - expect(delayedTask.isCompleted).toBe(false); - - // Remains unchanged: - // A single DelayedAsyncTask instance triggers exactly 1 `setTimeout` call, in the c'tor. - expect(setTimeoutSpy).toHaveBeenCalledTimes(1); - }); + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); + }); + + test('should capture uncaught exceptions thrown during execution', async () => { + const error = new Error("בוקה ומבוקה! ולב נמס! ופק ברכיים"); + const task = async (): Promise => { throw error; }; + + expect(setTimeoutSpy).toHaveBeenCalledTimes(0); + const delayedTask = new DelayedAsyncTask(task, MOCK_MS_DELAY_TILL_EXECUTION); + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); // Scheduled immediately on instantiation. + + expect(delayedTask.isPending).toBe(true); + // All other state getters should return false. + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isCompleted).toBe(false); + expect(delayedTask.isUncaughtRejectionOccurred).toBe(false); + expect(delayedTask.uncaughtRejection).toBe(undefined); + + jest.runOnlyPendingTimers(); + await delayedTask.awaitCompletionIfCurrentlyExecuting(); + + expect(delayedTask.isUncaughtRejectionOccurred).toBe(true); + expect(delayedTask.uncaughtRejection).toBe(error); + // All other state getters should return false. + expect(delayedTask.isPending).toBe(false); + expect(delayedTask.isAborted).toBe(false); + expect(delayedTask.isExecuting).toBe(false); + expect(delayedTask.isCompleted).toBe(false); + + // Unchanged behavior: + // A single `DelayedAsyncTask` instance triggers exactly one `setTimeout` call in the constructor. + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); }); + }); }); - \ No newline at end of file diff --git a/src/delayed-async-task.ts b/src/delayed-async-task.ts index 4d5727b..0d21ca7 100644 --- a/src/delayed-async-task.ts +++ b/src/delayed-async-task.ts @@ -35,9 +35,9 @@ type DelayedAsyncTaskStatus = */ export class DelayedAsyncTask { private _status: DelayedAsyncTaskStatus = "PENDING"; - private _timeout?: ReturnType; - private _uncaughtRejection?: UncaughtRejectionErrorType; - private _currentlyExecutingTaskPromise?: Promise; + private _timeout: ReturnType; + private _uncaughtRejection: UncaughtRejectionErrorType; + private _currentlyExecutingTaskPromise: Promise; private readonly _task: () => Promise; @@ -47,9 +47,9 @@ export class DelayedAsyncTask { * Schedules the provided task to start execution after the specified delay. * * @param task The async function to execute. - * @param msDelayTillExecution The delay in milliseconds before the task starts execution. + * @param delayTillExecutionMs The delay in milliseconds before the task starts execution. */ - constructor(task: () => Promise, msDelayTillExecution: number) { + constructor(task: () => Promise, delayTillExecutionMs: number) { this._task = task; // The `setTimeout` callback is deliberately non-async, to prevent a dangling promise. @@ -60,7 +60,7 @@ export class DelayedAsyncTask { this._status = "EXECUTING"; this._currentlyExecutingTaskPromise = this._handleTaskExecution(); }, - msDelayTillExecution + delayTillExecutionMs ); } @@ -147,7 +147,7 @@ export class DelayedAsyncTask { /** * awaitCompletionIfCurrentlyExecuting * - * This method resolves once the currently executing task finishes, or resolves immediately if the + * This method resolves once the currently executing task completes, or resolves immediately if the * task is not currently in-progress. * * This capability addresses the need for graceful and deterministic termination: