Prepare repository for merge
This commit is contained in:
106
articles/snippets/javascript-await-timeout.md
Normal file
106
articles/snippets/javascript-await-timeout.md
Normal file
@ -0,0 +1,106 @@
|
||||
---
|
||||
title: How can I add a timeout to a promise in JavaScript?
|
||||
shortTitle: Promise timeout
|
||||
type: question
|
||||
tags: [javascript,promise,timeout,class]
|
||||
author: chalarangelo
|
||||
cover: walking
|
||||
excerpt: Oftentimes you might need to add a timeout to a promise in JavaScript. Learn how to do this and more in this short guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Many times in the past I've found myself needing to add a timeout to a promise in JavaScript. `setTimeout()` is not exactly a perfect tool for the job, but it's easy enough to wrap it into a promise:
|
||||
|
||||
```js
|
||||
const awaitTimeout = delay =>
|
||||
new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
awaitTimeout(300).then(() => console.log('Hi'));
|
||||
// Logs 'Hi' after 300ms
|
||||
|
||||
const f = async () => {
|
||||
await awaitTimeout(300);
|
||||
console.log('Hi'); // Logs 'Hi' after 300ms
|
||||
};
|
||||
```
|
||||
|
||||
There's nothing particularly complicated about this code sample, really. All it does is use the `Promise` constructor to wrap `setTimeout()` and resolve the promise after `delay` ms. This can be a useful tool when some code has to stall for a given amount of time.
|
||||
|
||||
In order to add a timeout to another promise, however, there are two additional needs this utility has to satisfy. The first one is allowing the timeout promise to reject instead of resolving when provided a reason as a second argument. The other one is to create a wrapper function which will add the timeout to the promise:
|
||||
|
||||
```js
|
||||
const awaitTimeout = (delay, reason) =>
|
||||
new Promise((resolve, reject) =>
|
||||
setTimeout(
|
||||
() => (reason === undefined ? resolve() : reject(reason)),
|
||||
delay
|
||||
)
|
||||
);
|
||||
|
||||
const wrapPromise = (promise, delay, reason) =>
|
||||
Promise.race([promise, awaitTimeout(delay, reason)]);
|
||||
|
||||
wrapPromise(fetch('https://cool.api.io/data.json'), 3000, {
|
||||
reason: 'Fetch timeout',
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data.message);
|
||||
})
|
||||
.catch(data => console.log(`Failed with reason: ${data.reason}`));
|
||||
// Will either log the `message` if `fetch` completes in under 3000ms
|
||||
// or log an error message with the reason 'Fetch timeout' otherwise
|
||||
```
|
||||
|
||||
As you can see in this example, `reason` is used to determine if the timeout promise will resolve or reject. `awaitTimeout()` is then used to create a new promise and passed to `Promise.race()` along with the other promise to create a timeout.
|
||||
|
||||
This implementation definitely works, but we can take it a couple steps further. An obvious improvement is the addition of a way to clear a timeout, which requires storing the ids of any active timeouts. This, along with the need to make this utility self-contained both make a great case for using a `class`:
|
||||
|
||||
```js
|
||||
class Timeout {
|
||||
constructor() {
|
||||
this.ids = [];
|
||||
}
|
||||
|
||||
set = (delay, reason) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const id = setTimeout(() => {
|
||||
if (reason === undefined) resolve();
|
||||
else reject(reason);
|
||||
this.clear(id);
|
||||
}, delay);
|
||||
this.ids.push(id);
|
||||
});
|
||||
|
||||
wrap = (promise, delay, reason) =>
|
||||
Promise.race([promise, this.set(delay, reason)]);
|
||||
|
||||
clear = (...ids) => {
|
||||
this.ids = this.ids.filter(id => {
|
||||
if (ids.includes(id)) {
|
||||
clearTimeout(id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const myFunc = async () => {
|
||||
const timeout = new Timeout();
|
||||
const timeout2 = new Timeout();
|
||||
timeout.set(6000).then(() => console.log('Hello'));
|
||||
timeout2.set(4000).then(() => console.log('Hi'));
|
||||
timeout
|
||||
.wrap(fetch('https://cool.api.io/data.json'), 3000, {
|
||||
reason: 'Fetch timeout',
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data.message);
|
||||
})
|
||||
.catch(data => console.log(`Failed with reason: ${data.reason}`))
|
||||
.finally(() => timeout.clear(...timeout.ids));
|
||||
};
|
||||
// Will either log the `message` or log a 'Fetch timeout' error after 3000ms
|
||||
// The 6000ms timeout will be cleared before firing, so 'Hello' won't be logged
|
||||
// The 4000ms timeout will not be cleared, so 'Hi' will be logged
|
||||
```
|
||||
Reference in New Issue
Block a user