When using async await, sometimes we want to hold the promise in a variable, do something else, and then await it later. If the "something else" doesn't take very long this works fine:
try {
const pWillReject = rejectAfter(8000);
const someValue = await shortRunning();
doSomethingWith(someValue);
await pWillReject;
} catch (ex) {
// handle the error
console.log('caught', ex);
}
// continue
the error is caught by the catch as expected and code execution can continue
However, if the "something else" takes too long we may get the "Unhandled promise rejection" notice. Code execution is interuptted, the catch is not executed. This happens because, although we do eventually attach a handler, this isn't done until after the error event has already fired.
try {
const pWillReject = rejectAfter(8000);
const someValue = await longRunning();
doSomethingWith(someValue);
// we never get here
await pWillReject;
} catch (ex) {
//catch is not executed
console.log('caught', ex);
}
// no oppurtunity to continue
I don't want to add a global handler for the UnhandledPromiseRejection since in most cases I want the program to terminate in this situation (since it represents a coding error). However in this case, where the rejection is handled (just not quickly enough) the program should continue.
What is a good workaround for this issue?
The best I can think of is to attach a do-nothing handler in a non-invasive way, i.e.
const pWillReject = rejectAfter(8000);
pWillReject.catch(ex=>{}); // do nothing, errors will be handled later
But I don't like this
- it makes the code noisy
- it isn't self documenting - a comment is required
- most importantly - it risks a genuine coding error being missed. If the promise is never awaited it could fail silently.
Is there a better option?
full code example:
async function go() {
try {
const pWillReject = rejectAfter(8000);
const someValue = await shortRunning();
doSomethingWith(someValue);
await pWillReject;
} catch (ex) {
console.log('caught', ex);
}
console.log('success');
try {
const pWillReject = rejectAfter(8000);
const someValue = await longRunning();
doSomethingWith(someValue);
await pWillReject;
} catch (ex) {
console.log('caught', ex);
}
console.log('failure');
}
go().catch(ex=> {
console.log('global catch');
})
async function delay(ms) {
return new Promise((r,x)=> {
setTimeout(r, ms);
});
}
async function rejectAfter(ms) {
return new Promise((r,x)=> {
setTimeout(x, ms, new Error("whatever"));
});
}
function shortRunning() {
return delay(1000);
}
function longRunning() {
return delay(10000);
}
function doSomethingWith(obj) {
console.info(obj);
}
N.B. In my case I am using node, however I think the question applies equally to browsers.