AbortController aborting ahead of time - javascript

What I want to achieve is to abort previous request after user had changed filters.
I have tried this:
const API_URL = "https://www.example.com"
const controller = new AbortController();
const signal = controller.signal;
export const fetchData = async (filters) => {
// this console.log is fired only once
console.log("Below I thought it would abort a request if ongoing, and ignore otherwise");
controller.abort();
const response = await fetch(`${API_URL}/products?${filters}`, { method: "GET", signal });
return await response.json();
}
But what happens is my request being aborted even on first invoke, kind of ahead of time.
Other thing I tried is managing AbortController like so:
let controller;
let signal;
export const fetchData = async (filters) => {
if (controller) {
console.log("aborting")
controller.abort();
controller = null;
fetchData(filters); // won't work until I invoke this function here recursively
// and I expected something like
// controller = new AbortController();
// signal = controller.signal;
// ... then the rest of the function would work
} else {
controller = new AbortController();
signal = controller.signal;
}
const response = await fetch(`${API_URL}/products?${filters}`, { method: "GET", signal });
console.log("fetch fulfilled")
return await response.json();
}
But the above approach wouldn't work if I don't include recursive call of fetchData because calling controller.abort() caused the whole function to throw error and not perform until the end, after the if block.
And this would leave me happy if it worked, but "fetch fulfilled" is logged out twice. Why?

When there's already a controller, you're both calling fetchData (in the if branch) and doing the fetch later; that branch doesn't terminate the function. So you end up with two fetches and two "fulfilled" messages.
Simplifying the code should sort it out (see *** comments):
let controller; // Starts out with `undefined`
export const fetchData = async (filters) => {
// Abort any previous request
controller?.abort(); // *** Note the optional chaining
controller = new AbortController(); // *** Controller for this request
const response = await fetch(`${API_URL}/products?${filters}`, {
method: "GET",
signal: controller.signal, // *** Using this controller's signal
});
console.log("fetch fulfilled");
// *** Note: You need a check `response.ok` here
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return await response.json();
};

Related

Abort Axios request if function is called again

I'm trying to find a way to cancel axios requests if another request happens before the first request is complete.
I want it to work as a function. I have made a function looking like this:
import axios from "axios";
async function paginatedActivities(pageNumber) {
let controller = new AbortController();
try {
const fetchData = await axios.get(
`${process.env.SERVERBASEURL}/activities/paginatedactivities`,
{
signal: controller.signal,
params: { query: "query", pageNumber: pageNumber },
withCredentials: true,
}
);
return fetchData.data;
} catch (err) {
console.log(err);
if (axios.isCancel(err)) console.log("request cancelled");
}
}
export default paginatedActivities;
I can't figure out when to call "controller.abort()"..
The function could potentially be called again before it has done its job, and in such cases i'd need it to abort.. any suggestions? (without using the "useEffect"-hook..)
Make the controller a global variable so you can access it between calls of the function
let controller;
async function paginatedActivities(pageNumber) {
if (controller) controller.abort();
controller = new AbortController();
// Make the request
controller = undefined; // Reset the variable so it's not aborted next time
}

Abort previous remote call

I'm looking for a way to control repeated calls to the same webservice, in case of 2 identical remote requests, I want to abort the first one.
Not sure yet if I can use AbortController. It seems if I call controller.abort(); before fetch I abort the subsequent call..
<button class="download"> fetch </button>
<button class="abort"> abort </button>
<script>
var controller = new AbortController();
var signal = controller.signal;
var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');
downloadBtn.addEventListener('click', fetchJson);
abortBtn.addEventListener('click', function() {
controller.abort();
console.log('Download aborted');
});
function fetchJson() {
// TODO abort previous remote call here
// controller.abort(); // won't work
fetch("http://fakeapi.jsonparseronline.com/posts", {signal}).then( async response => {
console.log(await response.json())
}).catch(function(e) {
console.error(e)
})
}
</script>
You will have to create a new AbortController for each fetch, after you cancel the previous one. An AbortController's signal is set only once, similar to how you can resolve a promise only once, and it keeps the value afterwards. So if you don't renew your AbortController, the fetch will see an already-aborted signal.
let controller = new AbortController();
document.querySelector('.download').addEventListener('click', fetchJson);
document.querySelector('.abort').addEventListener('click', function() {
controller.abort();
console.log('Download aborted');
});
async function fetchJson() {
controller.abort(); // abort previous fetch here
controller = new AbortController(); // make new fetch abortable with a fresh AbortController
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const signal = controller.signal;
try {
const response = await fetch("http://fakeapi.jsonparseronline.com/posts", {signal});
console.log(await response.json());
} catch() {
console.error(e)
}
}

Can't make new fetch request after aborting previous

I need to change a parameters that defines what data should come from my requests, also this application needs to refresh on a regular time interval. If the user changes the parameter in the middle of an unfinished request things start to behave strange and some unexpected behavior occurs.
So my approach was to abort all previous requests before starting the new ones, but after using await controller.abort() it seems that the next requests are never triggered, Do I need to clear the signal or something like that?
const controller = new AbortController();
const async fetchData = (url, body = null) => {
let data;
const signal = controller.signal;
const headers = { ... };
response = await fetch(url, body ? {
method: "POST",
body: JSON.stringify(body),
signal,
headers
} : { headers, signal });;
data = await response.json()
return data
}
const firstData = await fetchData(url1, body1);
await controller.abort();
const secondData= await fetchData(url2, body2);
What happens is that secondData always is undefined, actually this second request never happens (looking on network traffic). If I stop source and try to run await fetchData(url2) after .abort() has executed it prompts an erros saying that Uncaught SyntaxError: await is only valid in async function or if I try to run it without await it returns a pending promise, but the actual request is nowhere to be seen in traffic tab.
Solved
Applying what was suggested on the ansewr I created wrapper on the function, to call new controllers everytime.
let controller = null;
let fetchData = null;
const initializeFetchData = () => {
const controller = new AbortController();
const async fetchData = (url, body = null) => {
let data;
const signal = controller.signal;
const headers = { ... };
response = await fetch(url, body ? {
method: "POST",
body: JSON.stringify(body),
signal,
headers
} : { headers, signal });;
data = await response.json()
return data
}
}
initializeFetchData();
const firstData = await fetchData(url1, body1);
controller.abort();
initializeFetchData();
const secondData= await fetchData(url2, body2);
You are using the sameAbortController for two different requests. After calling .abort() on theAbortController you have updated the state of it's AbortSignal which then renders the second request void.
You should use a separate AbortController for each request if you want this behavior. Of course, it is perfectly acceptable to reuse an AbortController for multiple fetch requests if you want to be able to abort all of them in one go.
A couple of other points...
.abort() is a synchronous method which returns void so you do not need the await prefix when calling .abort().
In your code example, the first request will never be aborted as you are awaiting the fetch request, which will complete before the .abort() is called.

How to return data with Axios

I am trying to extract data from an Axios call in Javascript. I can see that the data is being called successfully if I console.log() while inside of this block
Here is a link to a screenshot of console.log() if it is inside the axios call. https://imgur.com/a/ZLXnE2n
This data is correct, but I can't access it outside of the axios call.
const response = await axios
.get(url, config)
.then(function(response) {
data = response.data;
console.log(data)
})
However, I am unable to do anything with the data outside of the getRide_Uber function. How do I extract the response object to use in other parts of my code?
const axios = require("axios");
// Use the Uber API to estimate the cost between two
// locations specified via latitude and longitude coordinates.
getRide_Uber = async (addressOrigin, addressDestination) => {
let origin = await geocodeAddress(addressOrigin);
let destination = await geocodeAddress(addressDestination);
const url = "https://api.uber.com/v1.2/estimates/price";
const config = {
headers: {
Authorization: `Token ${process.env.UBER_SERVER_TOKEN}`
},
params: {
start_latitude: origin.lat,
start_longitude: origin.lon,
end_latitude: destination.lat,
end_longitude: destination.lon
}
};
const response = await axios
.get(url, config)
.then(function(response) {
data = response.data;
return data;
})
.catch(function(error) {
console.log(error);
});
return response;
};
// Initial code
// const rideData = getRide_Uber("Arlington Texas", "Fort Worth Texas");
// const ridePrices = rideData.prices;
// console.log(ridePrices);
// Code suggestion by #mralanlee
const a = (async() => {
const result = await getRide_Uber(start, dest);
return result;
})();
console.log(a); // Console just says <pending>
const prices = a.prices // undefined
Please let me know if anything needs clarification and I will be happy to explain. Thanks!
The data store is back into getRide_Uber.
You can have it accessible or returned globally like:
(async() => {
const result = await getRide_Uber(start, dest);
// or console.log(result)
return result;
})()
or
const a = (async() => {
const result = await getRide_Uber(start, dest);
return result;
})();
For the above solution, you would need to have this in a scape of another async function. This will not work at the global level because logging the result will not wait for the Promise to resolve. You must wrap it into an async function and await the anonymous function.
Edit: Clarification to add more clarity...
To use the 2nd solution, you could do the following:
// this async function wraps around your code, we'll call it something for a lack of a better name
// start and dest params are passed into getRide_Uber
async function something(start, dest) {
const result = await getRide_Uber(start, dest);
// You can make sure you have the resolved data here.
// console.log(result.prices)
return result;
};
// now call the function
something(start, dest);

How to update data after fetch?

I'm fetching data on componentDidMount in fetchData method. After that I'm trying to delete data with the method. I was trying to update date immediately in deleteUser method, but it doesn't work. How can I update this data after fetch with delete method?
fetchData = () => {
let url = `https://contact-browser.herokuapp.com/contacts`;
fetch(url, {method: 'get'}).then(resp => resp.json()).then((data) =>
this.setState({
data: Object.entries(data).map(([key,value])=>({key:Number(key),value}))
})
);
};
componentDidMount() {
this.fetchData();
}
deleteUser = (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
fetch(url, {method: 'delete'}).then(resp => console.log(resp));
this.fetchData();
};
Refetch your data after the delete fetch has fulfilled, this ensures that the data is fetched after the delete has fully resolved on the server.
deleteUser = (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
fetch(url, {method: 'delete'}).then(resp => {
this.fetchData();
});
};
It "did not work" because of the async behaviour of JS.
To be simple, when there is any waits in the code (like DB Call, API Call, etc), the JS code lets that particular to run and starts executing the next line. More on this - https://www.hongkiat.com/blog/synchronous-asynchronous-javascript/
In this case, since fetch is an i/o wait, the control moves to the next line - this.fetchData(). So, the fetchData() is called before delete may actually happens.
Following is another solution using async and await. More to read
deleteUser = async (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
let resp = await fetch(url, {method: 'delete'});
console.log(resp);
this.fetchData();
};
You can also use async and await
deleteUser = async (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
const deleted = await fetch(url, {method: 'delete'}).then(resp => console.log(resp));
if(deleted){
await this.fetchData();
}
};

Categories