Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/common",
"version": "5.32.0",
"version": "5.33.0",
"description": "The Athenna common helpers to use in any Node.js ESM project.",
"license": "MIT",
"author": "João Lenon <lenon@athenna.io>",
Expand Down
143 changes: 143 additions & 0 deletions src/helpers/Timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* @athenna/common
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/


export class TimeoutBuilder<T> {
private promise: Promise<T>;

private options: {
ms?: number;
onTimeout?: () => any;
onError?: (error: Error) => any;
};

public constructor(promise: Promise<T>) {
this.options = {};
this.promise = promise;
}

/**
* Define the timeout in milliseconds.
*
* @example
* ```ts
* const result = await Timeout.when(promise)
* .ms(1000)
* .race()
* ```
*/
public ms(ms: number) {
this.options.ms = ms;

return this;
}

/**
* Define the callback to be executed when an error occurs handling
* the promise.
*
* @example
* ```ts
* const result = await Timeout.when(promise)
* .ms(1000)
* .onError(error => ('fallback error'))
* .race()
* ```
*/
public onError(closure: (error: Error) => Promise<T>) {
this.options.onError = closure;

return this;
}

/**
* Define the callback to be executed when the timeout occurs.
*
* @example
* ```ts
* const result = await Timeout.when(promise)
* .ms(1000)
* .onTimeout(() => ('fallback timeout'))
* .race()
* ```
*/
public onTimeout(closure: () => any) {
this.options.onTimeout = closure;

return this;
}

/**
* Race the promise with timeout.
*
* @example
* ```ts
* const result = await Timeout.when(promise)
* .ms(1000)
* .onTimeout(() => ('fallback timeout'))
* .onError(error => ('fallback error'))
* .race()
* ```
*/
public async race() {
if (!this.options.ms) {
throw new Error("ms is required");
}

if (!this.options.onTimeout) {
throw new Error("onTimeout is required");
}

let timeoutId: NodeJS.Timeout;

Check failure on line 98 in src/helpers/Timeout.ts

View workflow job for this annotation

GitHub Actions / linux (21.x)

Promise constructor parameters must be named to match "^_?resolve$"

Check failure on line 98 in src/helpers/Timeout.ts

View workflow job for this annotation

GitHub Actions / windows (21.x)

Promise constructor parameters must be named to match "^_?resolve$"
const timeout = new Promise<never>((_, reject) => {
timeoutId = setTimeout(
() => reject(new Error("__RaceTimeout__")),
this.options.ms,
);
});

return Promise.race([this.promise, timeout])
.then((result) => {
clearTimeout(timeoutId);
return result;
})
.catch((error) => {
clearTimeout(timeoutId);

if (error.message === "__RaceTimeout__" && this.options.onTimeout) {
return this.options.onTimeout();
}

if (this.options.onError) {
return this.options.onError(error);
}

throw error;
});
}
}

export class Timeout {
/**
* Create a new timeout builder.
*
* @example
* ```ts
* const result = await Timeout.when(promise)
* .ms(1000)
* .onTimeout(() => ('fallback timeout'))
* .onError(error => ('fallback error'))
* .race()
* ```
*/
public static when<T>(promise: Promise<T>) {
return new TimeoutBuilder(promise);
}
}
Loading