It’s not a rare scenario where you need to invoke an asynchronous function that you expect to run in a reasonable period of time. If the function fails to return a value in the expected period of time then you should trigger an action such as dispelling to the user an error message, falling back to another backup function or just throwing a plain old good exception…
A Timeout that returns a promise
By creating a timeout function that returns a promise we are able to trigger a race between 2 functions using the Promise.race API function. The new promise will resolve as the timeout expires.
function timeoutResolver(ms){
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve(true);
}, ms)
});
}
The timeoutResolver will return a new promise that once it invoked triggers a timeout with the requested amount of time in milliseconds. Once the time has expired the promise will be resolved.
function timeoutResolver(ms) {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve(true);
}, ms);
});
}
timeoutResolver(1000).then(() => console.log("timer has expired"));
// After at least one seconed: timer has expired will apeare in the console
Basically, this is a timer that has been promisify.
Wait…. does it makes sense to resolve the promise?
That’s a very good question. Basically, since our goal is to finish the task in a reasonable amount of time if we will not win the race – this is not the expected outcome – we always strive to win. Let’s flip the promise around and reject it as the time out expires, now we will be able to catch an exception and learn that things didn’t turn out as expected.
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(function () {
reject();
}, ms);
});
}
timeoutResolver(1000).catch((err) => console.log("timer has expired"));
// After at least one second: timer has expired will in the console
A-wait? Switching to async-await
We can make things much easier to read if we switch to the async-await syntax and refactor
async main(){
try{
await timeoutResolver(1000);
}
catch(err){
console.log("timer has expired")
}
}
Promise race
We would like to do is create a race between our asynchronous function and the “promisfy” timer hoping our function will win and resolved before the timeout promise will expire and rejects – causing an exception to be thrown. We can use the promise API for this with the Promise.race
const ourFunction = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const timerPromise = timeout(1000);
Promise.race([ourFunction, timerPromise]).then((value) => {
console.log('we made it on time');
}).catch(()=>console.log('our function didnt finish on time'));
//. async-await version
async main(){
try {
await Promise.race([ourFunction, timerPromise]);
console.log('we made it on time');
}
catch(err){
console.log('our function didnt finish on time')
}
}
main();
Play with “ourFunction” timeout parameter to simulate what happens in both cases and you will be able to see that the race is on!
A challenge – try to implement the promise.race yourself
If you got until here and it seems to easy, let’s try to implement the Promie.race function ourselves. start by defining the input and output of the function:
Parameters – iterable
An iterable object, such as an Array
. See iterable.
Return value – A pending Promise
that asynchronously yields the value of the first promise in the given iterable to fulfill or reject.
Turns out we only need to return the first value – not even an array of undefined except to the relevant promise at the correct index that won the race:
we will use the spread operator – this will be an input of an array, once we got the array we need to iterate over it and trigger to each promise with the then method and resolve the first one that finishes – we also need to return a new Promise to the caller of the function
function race(...promises){
return new Promise((resolve, reject) => {
promises.forEach(p=>p.then((val=>resolve(val))));
});
}
The first promise that resolves will trigger the resolve function of the main function and the problem solved! nowhere comes a question of what happens if after the winner of the race has resolved and now the next in line resolved or worse – it rejects? In case of a resolve nothing but in case of a reject – seems like in node an unhandled rejection will cause node to crash in the feature.
(node:40401) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Better to just resolve this first one and keep a flag indicating that the promise has been resolved and just ignore the rest of the promises. Let’s refactor plus we will handle rejection if occurs
function race(...promises) {
let hasRaceEnded = false;
return new Promise((resolve, reject) => {
promises.forEach((p) => {
p.then((val) => {
if (!hasRaceEnded) {
hasRaceEnded = true;
resolve(val);
}
});
p.catch((err) => {
if (!hasRaceEnded) {
hasRaceEnded = true;
reject(val);
}
});
});
});
}
race(timeoutResolver(1000), timeoutResolver(750)).then((val) => console.log("I won the race ", val));
and we are done!
keep learning javascript with us!