Skip to content

Commit aee6f4f

Browse files
authored
Merge pull request #185 from AthennaIO/develop
feat(timeout): add helper to race promises with timeout
2 parents 6dbc5e6 + b493d72 commit aee6f4f

3 files changed

Lines changed: 146 additions & 3 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athenna/common",
3-
"version": "5.32.0",
3+
"version": "5.33.0",
44
"description": "The Athenna common helpers to use in any Node.js ESM project.",
55
"license": "MIT",
66
"author": "João Lenon <lenon@athenna.io>",

src/helpers/Timeout.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @athenna/common
3+
*
4+
* (c) João Lenon <lenon@athenna.io>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
11+
export class TimeoutBuilder<T> {
12+
private promise: Promise<T>;
13+
14+
private options: {
15+
ms?: number;
16+
onTimeout?: () => any;
17+
onError?: (error: Error) => any;
18+
};
19+
20+
public constructor(promise: Promise<T>) {
21+
this.options = {};
22+
this.promise = promise;
23+
}
24+
25+
/**
26+
* Define the timeout in milliseconds.
27+
*
28+
* @example
29+
* ```ts
30+
* const result = await Timeout.when(promise)
31+
* .ms(1000)
32+
* .race()
33+
* ```
34+
*/
35+
public ms(ms: number) {
36+
this.options.ms = ms;
37+
38+
return this;
39+
}
40+
41+
/**
42+
* Define the callback to be executed when an error occurs handling
43+
* the promise.
44+
*
45+
* @example
46+
* ```ts
47+
* const result = await Timeout.when(promise)
48+
* .ms(1000)
49+
* .onError(error => ('fallback error'))
50+
* .race()
51+
* ```
52+
*/
53+
public onError(closure: (error: Error) => Promise<T>) {
54+
this.options.onError = closure;
55+
56+
return this;
57+
}
58+
59+
/**
60+
* Define the callback to be executed when the timeout occurs.
61+
*
62+
* @example
63+
* ```ts
64+
* const result = await Timeout.when(promise)
65+
* .ms(1000)
66+
* .onTimeout(() => ('fallback timeout'))
67+
* .race()
68+
* ```
69+
*/
70+
public onTimeout(closure: () => any) {
71+
this.options.onTimeout = closure;
72+
73+
return this;
74+
}
75+
76+
/**
77+
* Race the promise with timeout.
78+
*
79+
* @example
80+
* ```ts
81+
* const result = await Timeout.when(promise)
82+
* .ms(1000)
83+
* .onTimeout(() => ('fallback timeout'))
84+
* .onError(error => ('fallback error'))
85+
* .race()
86+
* ```
87+
*/
88+
public async race() {
89+
if (!this.options.ms) {
90+
throw new Error("ms is required");
91+
}
92+
93+
if (!this.options.onTimeout) {
94+
throw new Error("onTimeout is required");
95+
}
96+
97+
let timeoutId: NodeJS.Timeout;
98+
99+
const timeout = new Promise<never>((_, reject) => {
100+
timeoutId = setTimeout(
101+
() => reject(new Error("__RaceTimeout__")),
102+
this.options.ms,
103+
);
104+
});
105+
106+
return Promise.race([this.promise, timeout])
107+
.then((result) => {
108+
clearTimeout(timeoutId);
109+
return result;
110+
})
111+
.catch((error) => {
112+
clearTimeout(timeoutId);
113+
114+
if (error.message === "__RaceTimeout__" && this.options.onTimeout) {
115+
return this.options.onTimeout();
116+
}
117+
118+
if (this.options.onError) {
119+
return this.options.onError(error);
120+
}
121+
122+
throw error;
123+
});
124+
}
125+
}
126+
127+
export class Timeout {
128+
/**
129+
* Create a new timeout builder.
130+
*
131+
* @example
132+
* ```ts
133+
* const result = await Timeout.when(promise)
134+
* .ms(1000)
135+
* .onTimeout(() => ('fallback timeout'))
136+
* .onError(error => ('fallback error'))
137+
* .race()
138+
* ```
139+
*/
140+
public static when<T>(promise: Promise<T>) {
141+
return new TimeoutBuilder(promise);
142+
}
143+
}

0 commit comments

Comments
 (0)