How to make a request call running in the background in JS? - javascript

I have the following code:
function getRegionList() {
return new Promise(function(resolve, reject) {
const config: ExtendedAxiosConfig = {
method: "GET",
url: "businesses/regions",
_addToken: false,
};
axios(config)
.then((res) => {
resolve(true);
store.set(LocalStoragekeys.Regions, res.data.data);
})
.catch((err) => {
reject(err);
});
});
}
It returns about 10k rows (that will be stored in the indexDB) and takes about 20sec to be finished. I call it in the application's loading step and it's like a bottleneck and increases the bounce-rate.
Any idea how can I make it running in the background? I mean, avoid stopping for it to get finished.

Related

Javascript JSON-Request timeout [duplicate]

I have a fetch-api POST request:
fetch(url, {
method: 'POST',
body: formData,
credentials: 'include'
})
I want to know what is the default timeout for this? and how can we set it to a particular value like 3 seconds or indefinite seconds?
Using a promise race solution will leave the request hanging and still consume bandwidth in the background and lower the max allowed concurrent request being made while it's still in process.
Instead use the AbortController to actually abort the request, Here is an example
const controller = new AbortController()
// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000)
fetch(url, { signal: controller.signal }).then(response => {
// completed request before timeout fired
// If you only wanted to timeout the request, not the response, add:
// clearTimeout(timeoutId)
})
Alternative you can use the newly added AbortSignal.timeout(5000)... but it is not well implemented in most browser right now. All green env have this now. You will lose control over manually closing the request. Both upload and download will have to finish within a total time of 5s
// a polyfill for it would be:
AbortSignal.timeout ??= function timeout(ms) {
const ctrl = new AbortController()
setTimeout(() => ctrl.close(), ms)
return ctrl.signal
}
fetch(url, { signal: AbortSignal.timeout(5000) })
AbortController can be used for other things as well, not only fetch but for readable/writable streams as well. More newer functions (specially promise based ones) will use this more and more. NodeJS have also implemented AbortController into its streams/filesystem as well. I know web bluetooth are looking into it also. Now it can also be used with addEventListener option and have it stop listening when the signal ends
Update since my original answer is a bit outdated I recommend using abort controller like implemented here: https://stackoverflow.com/a/57888548/1059828 or take a look at this really good post explaining abort controller with fetch: How do I cancel an HTTP fetch() request?
outdated original answer:
I really like the clean approach from this gist using Promise.race
fetchWithTimeout.js
export default function (url, options, timeout = 7000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), timeout)
)
]);
}
main.js
import fetch from './fetchWithTimeout'
// call as usual or with timeout as 3rd argument
// throw after max 5 seconds timeout error
fetch('http://google.com', options, 5000)
.then((result) => {
// handle result
})
.catch((e) => {
// handle errors and timeout error
})
Edit 1
As pointed out in comments, the code in the original answer keeps running the timer even after the promise is resolved/rejected.
The code below fixes that issue.
function timeout(ms, promise) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('TIMEOUT'))
}, ms)
promise
.then(value => {
clearTimeout(timer)
resolve(value)
})
.catch(reason => {
clearTimeout(timer)
reject(reason)
})
})
}
Original answer
It doesn't have a specified default; the specification doesn't discuss timeouts at all.
You can implement your own timeout wrapper for promises in general:
// Rough implementation. Untested.
function timeout(ms, promise) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("timeout"))
}, ms)
promise.then(resolve, reject)
})
}
timeout(1000, fetch('/hello')).then(function(response) {
// process response
}).catch(function(error) {
// might be a timeout error
})
As described in https://github.com/github/fetch/issues/175
Comment by https://github.com/mislav
Building on Endless' excellent answer, I created a helpful utility function.
const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
const controller = new AbortController();
const promise = fetch(url, { signal: controller.signal, ...options });
if (signal) signal.addEventListener("abort", () => controller.abort());
const timeout = setTimeout(() => controller.abort(), ms);
return promise.finally(() => clearTimeout(timeout));
};
If the timeout is reached before the resource is fetched then the fetch is aborted.
If the resource is fetched before the timeout is reached then the timeout is cleared.
If the input signal is aborted then the fetch is aborted and the timeout is cleared.
const controller = new AbortController();
document.querySelector("button.cancel").addEventListener("click", () => controller.abort());
fetchTimeout("example.json", 5000, { signal: controller.signal })
.then(response => response.json())
.then(console.log)
.catch(error => {
if (error.name === "AbortError") {
// fetch aborted either due to timeout or due to user clicking the cancel button
} else {
// network error or json parsing error
}
});
there's no timeout support in the fetch API yet. But it could be achieved by wrapping it in a promise.
for eg.
function fetchWrapper(url, options, timeout) {
return new Promise((resolve, reject) => {
fetch(url, options).then(resolve, reject);
if (timeout) {
const e = new Error("Connection timed out");
setTimeout(reject, timeout, e);
}
});
}
If you haven't configured timeout in your code, It will be the default request timeout of your browser.
1) Firefox - 90 seconds
Type about:config in Firefox URL field. Find the value corresponding to key network.http.connection-timeout
2) Chrome - 300 seconds
Source
EDIT: The fetch request will still be running in the background and will most likely log an error in your console.
Indeed the Promise.race approach is better.
See this link for reference Promise.race()
Race means that all Promises will run at the same time, and the race will stop as soon as one of the promises returns a value.
Therefore, only one value will be returned.
You could also pass a function to call if the fetch times out.
fetchWithTimeout(url, {
method: 'POST',
body: formData,
credentials: 'include',
}, 5000, () => { /* do stuff here */ });
If this piques your interest, a possible implementation would be :
function fetchWithTimeout(url, options, delay, onTimeout) {
const timer = new Promise((resolve) => {
setTimeout(resolve, delay, {
timeout: true,
});
});
return Promise.race([
fetch(url, options),
timer
]).then(response => {
if (response.timeout) {
onTimeout();
}
return response;
});
}
A more clean way to do it is actually in MDN: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal#aborting_a_fetch_operation_with_a_timeout
try {
await fetch(url, { signal: AbortSignal.timeout(5000) });
} catch (e) {
if (e.name === "TimeoutError") {
console.log('5000 ms timeout');
}
}
Here's a SSCCE using NodeJS which will timeout after 1000ms:
import fetch from 'node-fetch';
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 1000); // will time out after 1000ms
fetch('https://www.yourexample.com', {
signal: controller.signal,
method: 'POST',
body: formData,
credentials: 'include'
}
)
.then(response => response.json())
.then(json => console.log(json))
.catch(err => {
if(err.name === 'AbortError') {
console.log('Timed out');
}}
)
.finally( () => {
clearTimeout(timeout);
});
Using AbortController and setTimeout;
const abortController = new AbortController();
let timer: number | null = null;
fetch('/get', {
signal: abortController.signal, // Content to abortController
})
.then(res => {
// response success
console.log(res);
if (timer) {
clearTimeout(timer); // clear timer
}
})
.catch(err => {
if (err instanceof DOMException && err.name === 'AbortError') {
// will return a DOMException
return;
}
// other errors
});
timer = setTimeout(() => {
abortController.abort();
}, 1000 * 10); // Abort request in 10s.
This is a fragment in #fatcherjs/middleware-aborter.
By using fatcher, it can easy to abort a fetch request.
import { aborter } from '#fatcherjs/middleware-aborter';
import { fatcher, isAbortError } from 'fatcher';
fatcher({
url: '/bar/foo',
middlewares: [
aborter({
timeout: 10 * 1000, // 10s
onAbort: () => {
console.log('Request is Aborted.');
},
}),
],
})
.then(res => {
// Request success in 10s
console.log(res);
})
.catch(err => {
if (isAbortError(err)) {
//Run error when request aborted.
console.error(err);
}
// Other errors.
});
fetchTimeout (url,options,timeout=3000) {
return new Promise( (resolve, reject) => {
fetch(url, options)
.then(resolve,reject)
setTimeout(reject,timeout);
})
}
You can create a timeoutPromise wrapper
function timeoutPromise(timeout, err, promise) {
return new Promise(function(resolve,reject) {
promise.then(resolve,reject);
setTimeout(reject.bind(null,err), timeout);
});
}
You can then wrap any promise
timeoutPromise(100, new Error('Timed Out!'), fetch(...))
.then(...)
.catch(...)
It won't actually cancel an underlying connection but will allow you to timeout a promise.
Reference
Proper error handling tips
Normal practice:
To add timeout support most of the time it is suggested to introduce a Promise utility function like this:
function fetchWithTimeout(resource, { signal, timeout, ...options } = {}) {
const controller = new AbortController();
if (signal != null) signal.addEventListener("abort", controller.abort);
const id = timeout != null ? setTimeout(controller.abort, timeout) : undefined;
return fetch(resource, {
...options,
signal: controller.signal
}).finally(() => {
if (id != null) clearTimeout(id);
});
}
Calling controller.abort or rejecting the promise inside the setTimeout callback function distorts the stack trace.
This is suboptimal, since one would have to add boilerplate error handlers with log messages in the functions calling the fetch method if post-error log analysis is required.
Good expertise:
To preserve the error along with it's stack trace one can apply the following technique:
function sleep(ms = 0, signal) {
return new Promise((resolve, reject) => {
const id = setTimeout(() => resolve(), ms);
signal?.addEventListener("abort", () => {
clearTimeout(id);
reject();
});
});
}
async function fetch(
resource,
options
) {
const { timeout, signal, ...ropts } = options ?? {};
const controller = new AbortController();
let sleepController;
try {
signal?.addEventListener("abort", () => controller.abort());
const request = nodeFetch(resource, {
...ropts,
signal: controller.signal,
});
if (timeout != null) {
sleepController = new AbortController();
const aborter = sleep(timeout, sleepController.signal);
const race = await Promise.race([aborter, request]);
if (race == null) controller.abort();
}
return request;
} finally {
sleepController?.abort();
}
}
(async () => {
try {
await fetchWithTimeout(new URL(window.location.href), { timeout: 5 });
} catch (error) {
console.error("Error in test", error);
}
})();
Using c-promise2 lib the cancellable fetch with timeout might look like this one (Live jsfiddle demo):
import CPromise from "c-promise2"; // npm package
function fetchWithTimeout(url, {timeout, ...fetchOptions}= {}) {
return new CPromise((resolve, reject, {signal}) => {
fetch(url, {...fetchOptions, signal}).then(resolve, reject)
}, timeout)
}
const chain = fetchWithTimeout("https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=10s", {timeout: 5000})
.then(request=> console.log('done'));
// chain.cancel(); - to abort the request before the timeout
This code as a npm package cp-fetch

Halt Execution of Network Request If It Takes Too Long?

I have some code that basically calls fetch in Javascript. The third party services sometimes take too long to return a response and in an attempt to be more user-friendly, I want to be able to either post a message or stop the connection from being open after N milliseconds.
I had recently come across this post:
Skip the function if executing time too long. JavaScript
But did not have much luck and had issues getting it to work with the below code. I was also hoping that there was a more modern approach to do such a task, maybe using async/await?
module.exports = (url, { ...options } = {}) => {
return fetch(url, {
...options
})
}
You can use a combination of Promise.race and AbortController, here is an example:
function get(url, timeout) {
const controller = new AbortController();
return Promise.race([fetch(url, {
signal: controller.signal
}), new Promise(resolve => {
setTimeout(() => {
resolve("request was not fulfilled in time");
controller.abort();
}, timeout)
})]);
}
(async() => {
const result = await get("https://example.com", 1);
console.log(result);
})();
The native Fetch API doesn't have a timeout built in like something like axios does, but you can always create a wrapper function that wraps the fetch call to implement this.
Here is an example:
const fetchWithTimeout = (timeout, fetchConfig) => {
const FETCH_TIMEOUT = timeout || 5000;
let didTimeOut = false;
return new Promise(function(resolve, reject) {
const timeout = setTimeout(function() {
didTimeOut = true;
reject(new Error('Request timed out'));
}, FETCH_TIMEOUT);
fetch('url', fetchConfig)
.then(function(response) {
// cleanup timeout
clearTimeout(timeout);
if(!didTimeOut) {
// fetch request was good
resolve(response);
}
})
.catch(function(err) {
// Rejection already happened with setTimeout
if(didTimeOut) return;
// Reject with error
reject(err);
});
})
.then(function() {
// Request success and no timeout
})
.catch(function(err) {
//error
});
}
from here https://davidwalsh.name/fetch-timeout

How to timeout function when a rest call fails in node js?

I have to timeout the function uploadData whenever the rest call fails due to some condition. I have tried with setInterval in a catch block but it didn't give me the required results. So how can I code to timeout my function in a failure condition within 5000ms? This is my code:
uploadData(filename,callback){
formData={
'filename'=fs.createReadStream(filename)
}
options{
methiod:POST,
url:url,
auth:this.auth,
headrers:this.headers,
formData:formData
}
rp(options).then((repos)=>{
var response={
'file':filename,
'status':'success',
'message':repos,
};
return callback(response);
}).catch(fn=setInterval((err)=>{
var response={
'file':filename,
'status':'failes',
'message':err.message,
}
return callback(response);
},5000));
}
A good way to accomplish such a feature is to use Promise.race with two promises: first is the one which makes a request, and the second is a timeout promise which resolves after a fixed time. Example:
const timeout = new Promise((resolve) => {
setTimeout(() => resolve({ timeout: true }), 5000);
});
const formData = {
'filename'=fs.createReadStream(filename)
}
const options = {
method: 'POST',
url,
auth: this.auth,
headrers: this.headers,
formData: formData
}
const request = rp(options);
// The first one to resolve will be passed to the `.then()` callback
Promise.race([request, timeout]).then((response) => {
if (response.timeout === true) {
return console.log('timeout');
}
console.log('api response', response);
});

Node/Express promise doesn't get re-called on subsequent route requests

The Problem
I've got a route (/mediumData) that gets called on every page reload of a website. Here is the express router handler for the route:
router.get("/mediumData", (request, response) => {
getMediumData
.then(mediumData => {
response.json(mediumData)
})
.catch(error => {
response.send(error)
})
});
The issue I'm running into is that the promise getMediumData only gets called when the server restarts, and not on every page load like intended.
What I've Tried
When I've tried debugging the code with console.log statements, I found out that the console.log was being executed inside of the getMediumData.then handler (on page refreshes). However, the getMediumData promise would not execute any console.log statements on page refreshes (only on server restarts would the console.log be executed).
Here is an example of what I'm talking about regarding my console.log debugging:
getMediumData.then handler
router.get("/mediumData", (request, response) => {
getMediumData
.then(mediumData => {
console.log("This text gets outputted on every page refresh.");
response.json(mediumData)
})
.catch(error => {
response.send(error)
})
});
getMediumData promise
const getMediumData = new Promise((resolve, reject) => {
console.log("This text only gets outputted on server restarts.");
https
.get(
// Some stuff...
)
});
Promises can only be resolved/rejected once, it's just how they work. So a simple solution here would be to wrap your existing getMediumData promise in a function that creates a new promise for every request.
For example:
const getMediumData = () => new Promise((resolve, reject) => {
https
.get(
// Some stuff...
)
});
router.get("/mediumData", (request, response) => {
getMediumData().then(mediumData => {
response.json(mediumData)
}).catch(error => {
response.send(error)
});
});
The code wrapped by the getMediumData promise executes only once. To execute it in every request, you could move the assignment (i.e. getMediumData = new Promise(...) into the body of the route.
Alternatively, you can wrap the promise creation logic into a function returning a new promise each time and use that function in the route.
const getMediumDataPromise = () => new Promise(
(resolve, reject) => {
console.log("This text only gets outputted on server restarts.");
https
.get(
// Some stuff...
)
}
);
router.get("/mediumData", (request, response) => {
getMediumDataPromise()
.then(mediumData => {
console.log("This text gets outputted on every page refresh.");
response.json(mediumData)
})
.catch(error => {
response.send(error)
})
});

Reactjs and redux - How to prevent excessive api calls from a live-search component?

I have created this live-search component:
class SearchEngine extends Component {
constructor (props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
}
handleChange (e) {
this.props.handleInput(e.target.value); //Redux
}
handleSearch (input, token) {
this.props.handleSearch(input, token) //Redux
};
componentWillUpdate(nextProps) {
if(this.props.input !== nextProps.input){
this.handleSearch(nextProps.input, this.props.loginToken);
}
}
render () {
let data= this.props.result;
let searchResults = data.map(item=> {
return (
<div key={item.id}>
<h3>{item.title}</h3>
<hr />
<h4>by {item.artist}</h4>
<img alt={item.id} src={item.front_picture} />
</div>
)
});
}
return (
<div>
<input name='input'
type='text'
placeholder="Search..."
value={this.props.input}
onChange={this.handleChange} />
<button onClick={() => this.handleSearch(this.props.input, this.props.loginToken)}>
Go
</button>
<div className='search_results'>
{searchResults}
</div>
</div>
)
}
It is part of a React & Redux app I'm working on and is connected to the Redux store.
The thing is that when a user types in a search query, it fires an API call for each of the characters in the input, and creating an excessive API calling, resulting in bugs like showing results of previous queries, not following up with the current search input.
My api call (this.props.handleSearch):
export const handleSearch = (input, loginToken) => {
const API= `https://.../api/search/vector?query=${input}`;
}
return dispatch => {
fetch(API, {
headers: {
'Content-Type': 'application/json',
'Authorization': loginToken
}
}).then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(res => res.json()).then(data => {
if(data.length === 0){
dispatch(handleResult('No items found.'));
}else{
dispatch(handleResult(data));
}
}).catch((error) => {
console.log(error);
});
}
};
My intention is that it would be a live-search, and update itself based on user input. but I am trying to find a way to wait for the user to finish his input and then apply the changes to prevent the excessive API calling and bugs.
Suggestions?
EDIT:
Here's what worked for me.
Thanks to Hammerbot's amazing answer I managed to create my own class of QueueHandler.
export default class QueueHandler {
constructor () { // not passing any "queryFunction" parameter
this.requesting = false;
this.stack = [];
}
//instead of an "options" object I pass the api and the token for the "add" function.
//Using the options object caused errors.
add (api, token) {
if (this.stack.length < 2) {
return new Promise ((resolve, reject) => {
this.stack.push({
api,
token,
resolve,
reject
});
this.makeQuery()
})
}
return new Promise ((resolve, reject) => {
this.stack[1] = {
api,
token,
resolve,
reject
};
this.makeQuery()
})
}
makeQuery () {
if (! this.stack.length || this.requesting) {
return null
}
this.requesting = true;
// here I call fetch as a default with my api and token
fetch(this.stack[0].api, {
headers: {
'Content-Type': 'application/json',
'Authorization': this.stack[0].token
}
}).then(response => {
this.stack[0].resolve(response);
this.requesting = false;
this.stack.splice(0, 1);
this.makeQuery()
}).catch(error => {
this.stack[0].reject(error);
this.requesting = false;
this.stack.splice(0, 1);
this.makeQuery()
})
}
}
I made a few changes in order for this to work for me (see comments).
I imported it and assigned a variable:
//searchActions.js file which contains my search related Redux actions
import QueueHandler from '../utils/QueueHandler';
let queue = new QueueHandler();
Then in my original handleSearch function:
export const handleSearch = (input, loginToken) => {
const API= `https://.../api/search/vector?query=${input}`;
}
return dispatch => {
queue.add(API, loginToken).then... //queue.add instead of fetch.
Hope this helps anyone!
I think that they are several strategies to handle the problem. I'm going to talk about 3 ways here.
The two first ways are "throttling" and "debouncing" your input. There is a very good article here that explains the different techniques: https://css-tricks.com/debouncing-throttling-explained-examples/
Debounce waits a given time to actually execute the function you want to execute. And if in this given time you make the same call, it will wait again this given time to see if you call it again. If you don't, it will execute the function. This is explicated with this image (taken from the article mentioned above):
Throttle executes the function directly, waits a given time for a new call and executes the last call made in this given time. The following schema explains it (taken from this article http://artemdemo.me/blog/throttling-vs-debouncing/):
I was using those first techniques at first but I found some downside to it. The main one was that I could not really control the rendering of my component.
Let's imagine the following function:
function makeApiCall () {
api.request({
url: '/api/foo',
method: 'get'
}).then(response => {
// Assign response data to some vars here
})
}
As you can see, the request uses an asynchronous process that will assign response data later. Now let's imagine two requests, and we always want to use the result of the last request that have been done. (That's what you want in a search input). But the result of the second request comes first before the result of the first request. That will result in your data containing the wrong response:
1. 0ms -> makeApiCall() -> 100ms -> assigns response to data
2. 10ms -> makeApiCall() -> 50ms -> assigns response to data
The solution for that to me was to create some sort of "queue". The behaviour of this queue is:
1 - If we add a task to the queue, the task goes in front of the queue.
2 - If we add a second task to the queue, the task goes in the second position.
3 - If we add a third task to the queue, the task replaces the second.
So there is a maximum of two tasks in the queue. As soon as the first task has ended, the second task is executed etc...
So you always have the same result, and you limit your api calls in function of many parameters. If the user has a slow internet connexion, the first request will take some time to execute, so there won't be a lot of requests.
Here is the code I used for this queue:
export default class HttpQueue {
constructor (queryFunction) {
this.requesting = false
this.stack = []
this.queryFunction = queryFunction
}
add (options) {
if (this.stack.length < 2) {
return new Promise ((resolve, reject) => {
this.stack.push({
options,
resolve,
reject
})
this.makeQuery()
})
}
return new Promise ((resolve, reject) => {
this.stack[1] = {
options,
resolve,
reject
}
this.makeQuery()
})
}
makeQuery () {
if (! this.stack.length || this.requesting) {
return null
}
this.requesting = true
this.queryFunction(this.stack[0].options).then(response => {
this.stack[0].resolve(response)
this.requesting = false
this.stack.splice(0, 1)
this.makeQuery()
}).catch(error => {
this.stack[0].reject(error)
this.requesting = false
this.stack.splice(0, 1)
this.makeQuery()
})
}
}
You can use it like this:
// First, you create a new HttpQueue and tell it what to use to make your api calls. In your case, that would be your "fetch()" function:
let queue = new HttpQueue(fetch)
// Then, you can add calls to the queue, and handle the response as you would have done it before:
queue.add(API, {
headers: {
'Content-Type': 'application/json',
'Authorization': loginToken
}
}).then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(res => res.json()).then(data => {
if(data.length === 0){
dispatch(handleResult('No vinyls found.'));
}else{
dispatch(handleResult(data));
}
}).catch((error) => {
console.log(error);
});
}

Categories

Resources