I'm with Node.js and TypeScript and I'm using async/await
.
This is my test case:
async function doSomethingInSeries() {
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
}
I'd like to set a timeout for the overall function. I.e. if res1
takes 2 seconds, res2
takes 0.5 seconds, res3
takes 5 seconds I'd like to have a timeout that after 3 seconds let me throw an error.
With a normal setTimeout
call is a problem because the scope is lost:
async function doSomethingInSeries() {
const timerId = setTimeout(function() {
throw new Error('timeout');
});
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
clearTimeout(timerId);
return 'simle';
}
And I cannot catch it with normal Promise.catch
:
doSomethingInSeries().catch(function(err) {
// errors in res1, res2, res3 will be catched here
// but the setTimeout thing is not!!
});
Any ideas on how to resolve?
With the help of Node. js development team, we are now able to use async/await syntax while dealing with setTimeout() functions. This feature was initially implemented in Node v.
setTimeout() is an asynchronous function, meaning that the timer function will not pause execution of other functions in the functions stack. In other words, you cannot use setTimeout() to create a "pause" before the next function in the function stack fires.
The keyword await is used to wait for a Promise. It can only be used inside an async function. This keyword makes JavaScript wait until that promise settles and returns its result. Here is an example with a promise that resolves in 2 seconds.
To make a promise from setTimeout with JavaScript, we can use the Promise constructor. const later = (delay) => { return new Promise((resolve) => { setTimeout(resolve, delay); }); }; to create the later function which returns a promise we create with the Promise constructor.
@tinkerr "timeout needs to be declared async if it needs to be awaited" - Nope. A function only needs to return a promise that can be awaited (or actually, a thenable is enough). How it achieves that is up to the implementation of the function, it does not need to be an async function. @naisanza No, async / await is based on promises.
Say you have an awaitable object, and you want to await it, but with a timeout. How would you build that? What you can do is use a when_any -like function in combination with a timeout coroutine. For C# this would be something like await Task.WhenAny ( DoSomethingAsync (), Task.Delay (TimeSpan.FromSeconds (1)));
setTimeout is not an async function, so you can't use it with ES7 async-await. But you could implement your sleep function using ES6 Promise: function sleep (fn, par) { return new Promise ( (resolve) => { // wait 3s before calling fn (par) setTimeout ( () => resolve (fn (par)), 3000) }) }
What you can do is use a when_any -like function in combination with a timeout coroutine. For C# this would be something like await Task.WhenAny ( DoSomethingAsync (), Task.Delay (TimeSpan.FromSeconds (1))); The WhenAny method completes as soon as any of the passed-in tasks completes.
You can use Promise.race
to make a timeout:
Promise.race([
doSomethingInSeries(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 11.5e3))
]).catch(function(err) {
// errors in res1, res2, res3 and the timeout will be caught here
})
You cannot use setTimeout
without wrapping it in a promise.
Ok I found this way:
async function _doSomethingInSeries() {
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
}
async function doSomethingInSeries(): Promise<any> {
let timeoutId;
const delay = new Promise(function(resolve, reject){
timeoutId = setTimeout(function(){
reject(new Error('timeout'));
}, 1000);
});
// overall timeout
return Promise.race([delay, _doSomethingInSeries()])
.then( (res) => {
clearTimeout(timeoutId);
return res;
});
}
Anyone errors?
The things that smells a bit to me is that using Promises as asynchronicity strategy will send us to allocate too many object that some other strategy needs but this is off-topic.
Problem with @Bergi answer that doSomethingInSeries
continues executing even if you already rejected the promise. It is much better to cancel it.
LATEST ANSWER
You can try use AbortController for that. Check the old answer to see how to use it - api is similar.
Keep in mind that task is not cancelled immediately, so continuation (awaiting, then or catch) is not called exactly after timeout.
To guarantee that you can combine this and @Bergi approach.
OLD ANSWER
This is how it should look like:
async const doSomethingInSeries = (cancellationToken) => {
cancellationToken.throwIfCancelled();
const res1 = await callApi();
cancellationToken.throwIfCancelled();
const res2 = await persistInDB(res1);
cancellationToken.throwIfCancelled();
const res3 = await doHeavyComputation(res1);
cancellationToken.throwIfCancelled();
return 'simle';
}
Here is simple implementation:
const makeCancellationToken = (tag) => {
let cancelled = false;
return {
isCancelled: () => cancelled,
cancel: () => {
cancelled = true;
},
throwIfCancelled: () => {
if (cancelled) {
const error = new Error(`${tag ?? 'Task'} cancelled`);
error.cancelled = true;
throw error;
}
}
}
}
And finally usage:
const cancellationToken = makeCancellationToken('doSomething')
setTimeout(cancellationToken.cancel, 5000);
try {
await doSomethingInSeries(cancellationToken);
} catch (error) {
if (error.cancelled) {
// handle cancellation
}
}
Keep in mind that task is not cancelled immediately, so continuation (awaiting, then or catch) is not called exactly after 5 secs.
To guarantee that you can combine this and @Bergi approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With