Here is my answer which essentially simplifies Martin's answer and is based upon the same tutorial.
Timeout wrapper for a promise:
const timeout = (prom, time) => {
const timeoutError = new Error(`execution time has exceeded the allowed time frame of ${time} ms`);
let timer; // will receive the setTimeout defined from time
timeoutError.name = "TimeoutErr";
return Promise.race([
prom,
new Promise((_r, rej) => timer = setTimeout(rej, time, timeoutError)) // returns the defined timeoutError in case of rejection
]).catch(err => { // handle errors that may occur during the promise race
throw(err);
}) .finally(() => clearTimeout(timer)); // clears timer
}
A promise for testing purposes:
const fn = async (a) => { // resolves in 500 ms or throw an error if a == true
if (a == true) throw new Error('test error');
await new Promise((res) => setTimeout(res, 500));
return "p2";
}
Now here is a test function:
async function test() {
let result;
try { // finishes before the timeout
result = await timeout(fn(), 1000); // timeouts in 1000 ms
console.log('• Returned Value :', result, '\n'); // result = p2
} catch(err) {
console.log('• Captured exception 0 : \n ', err, '\n');
}
try { // don't finish before the timeout
result = await timeout(fn(), 100); // timeouts in 100 ms
console.log(result); // not executed as the timeout error was triggered
} catch (err) {
console.log('• Captured exception 1 : \n ', err, '\n');
}
try { // an error occured during fn execution time
result = await timeout(fn(true), 100); // fn will throw an error
console.log(result); // not executed as an error occured
} catch (err) {
console.log('• Captured exception 2 : \n ', err, '\n');
}
}
that will produce this output:
• Returned Value : p2
• Captured exception 1 :
TimeoutErr: execution time has exceeded the allowed time frame of 100 ms
at C:\...\test-promise-race\test.js:33:34
at async test (C:\...\test-promise-race\test.js:63:18)
• Captured exception 2 :
Error: test error
at fn (C:\...\test-promise-race\test.js:45:26)
at test (C:\...\test-promise-race\test.js:72:32)
If you don't want to use try ... catch
instructions in the test
function you can alternatively replace the throw
instructions in the catch
part of the timeout promise wrapper by return
.
By doing so the result
variable will receive the error that is throwed otherwise.
You can then use this to detect if the result
variable actually contains an error.
if (result instanceof Error) {
// there was an error during execution
}
else {
// result contains the value returned by fn
}
If you want to check if the error is relative to the defined timeout you will have to check the error.name
value for "TimeoutErr"
.