Axios cancel token cancelling request before even called - javascript

So I am implementing axios call cancelation in the project. Right now looking at axios documentation it seems pretty straight forward https://github.com/axios/axios#cancellation
So I did define variables on the top of my Vue component like
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
obviously on top of that is import axios from 'axios';
Then I have a method of fetching the API
On the top of the method I want to cancel out the request in case it is running so the last one cancels out if the user spams the filtering.
async fetchPartners(inputToClear) {
source.cancel();
...
try {
const response = await axios.get(`../partners?limit=1000${this.createRequestString()}`, {
cancelToken: source.token
});
// Here you can see I did add the cancelToken to the request
this.partners = response.data.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
const fetchErrors = this.utilGlobalHandleErrorMessages(error);
this.utilGlobalDisplayMessage(fetchErrors.message, { type: 'error' });
return [];
} finally {
...
}
},
So it is pretty straight forward, just took the code from axios documentation I gave you above, it should be working by logic. But what is actually happening, it doesn't even allow me to fetch the call, it is already cancelled out before I can call it. On console it shows me
Request canceled undefined
It just catches the error as if I am cancelling the call, but how can it be, because I am source.cancel() before the call.
Anyone has any idea?

I hope you should throttle your requests instead of canceling the request.
Could you please try the following if throttle does not suit your requirement?
const CancelToken = axios.CancelToken;
let source;
async fetchPartners(inputToClear) {
if(source){
source.cancel();
}
...
source = CancelToken.source();
try {
const response = await axios.get(`../partners?limit=1000${this.createRequestString()}`, {
cancelToken: source.token
});
// Here you can see I did add the cancelToken to the request
this.partners = response.data.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
const fetchErrors = this.utilGlobalHandleErrorMessages(error);
this.utilGlobalDisplayMessage(fetchErrors.message, {
type: 'error'
});
return [];
} finally {
...
}
}

Related

Avoid multiple time api calls when user clicks on button accidently multiple times in javascript/React.js [duplicate]

I use axios for ajax requests and reactJS + flux for render UI. In my app there is third side timeline (reactJS component). Timeline can be managed by mouse's scroll. App sends ajax request for the actual data after any scroll event. Problem that processing of request at server can be more slow than next scroll event. In this case app can have several (2-3 usually) requests that already is deprecated because user scrolls further. it is a problem because every time at receiving of new data timeline begins redraw. (Because it's reactJS + flux) Because of this, the user sees the movement of the timeline back and forth several times. The easiest way to solve this problem, it just abort previous ajax request as in jQuery. For example:
$(document).ready(
var xhr;
var fn = function(){
if(xhr && xhr.readyState != 4){
xhr.abort();
}
xhr = $.ajax({
url: 'ajax/progress.ftl',
success: function(data) {
//do something
}
});
};
var interval = setInterval(fn, 500);
);
How to cancel/abort requests in axios?
Axios does not support canceling requests at the moment. Please see this issue for details.
UPDATE: Cancellation support was added in axios v0.15.
EDIT: The axios cancel token API is based on the withdrawn cancelable promises proposal.
UPDATE 2022: Starting from v0.22.0 Axios supports AbortController to cancel requests in fetch API way:
Example:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
Using useEffect hook:
useEffect(() => {
const ourRequest = Axios.CancelToken.source() // <-- 1st step
const fetchPost = async () => {
try {
const response = await Axios.get(`endpointURL`, {
cancelToken: ourRequest.token, // <-- 2nd step
})
console.log(response.data)
setPost(response.data)
setIsLoading(false)
} catch (err) {
console.log('There was a problem or request was cancelled.')
}
}
fetchPost()
return () => {
ourRequest.cancel() // <-- 3rd step
}
}, [])
Note: For POST request, pass cancelToken as 3rd argument
Axios.post(`endpointURL`, {data}, {
cancelToken: ourRequest.token, // 2nd step
})
Typically you want to cancel the previous ajax request and ignore it's coming response, only when a new ajax request of that instance is started, for this purpose, do the following:
Example: getting some comments from API:
// declare an ajax request's cancelToken (globally)
let ajaxRequest = null;
function getComments() {
// cancel previous ajax if exists
if (ajaxRequest ) {
ajaxRequest.cancel();
}
// creates a new token for upcomming ajax (overwrite the previous one)
ajaxRequest = axios.CancelToken.source();
return axios.get('/api/get-comments', { cancelToken: ajaxRequest.token }).then((response) => {
console.log(response.data)
}).catch(function(err) {
if (axios.isCancel(err)) {
console.log('Previous request canceled, new request is send', err.message);
} else {
// handle error
}
});
}
import React, { Component } from "react";
import axios from "axios";
const CancelToken = axios.CancelToken;
let cancel;
class Abc extends Component {
componentDidMount() {
this.Api();
}
Api() {
// Cancel previous request
if (cancel !== undefined) {
cancel();
}
axios.post(URL, reqBody, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
}),
})
.then((response) => {
//responce Body
})
.catch((error) => {
if (axios.isCancel(error)) {
console.log("post Request canceled");
}
});
}
render() {
return <h2>cancel Axios Request</h2>;
}
}
export default Abc;
There is really nice package with few examples of usage called axios-cancel.
I've found it very helpful.
Here is the link: https://www.npmjs.com/package/axios-cancel
https://github.com/axios/axios#cancellation
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
let url = 'www.url.com'
axios.get(url, {
progress: false,
cancelToken: source.token
})
.then(resp => {
alert('done')
})
setTimeout(() => {
source.cancel('Operation canceled by the user.');
},'1000')
This is how I did it using promises in node. Pollings stop after making the first request.
var axios = require('axios');
var CancelToken = axios.CancelToken;
var cancel;
axios.get('www.url.com',
{
cancelToken: new CancelToken(
function executor(c) {
cancel = c;
})
}
).then((response) =>{
cancel();
})
Using cp-axios wrapper you able to abort your requests with three diffent types of the cancellation API:
1. Promise cancallation API (CPromise):
Live browser example
const cpAxios= require('cp-axios');
const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=5s';
const chain = cpAxios(url)
.timeout(5000)
.then(response=> {
console.log(`Done: ${JSON.stringify(response.data)}`)
}, err => {
console.warn(`Request failed: ${err}`)
});
setTimeout(() => {
chain.cancel();
}, 500);
2. Using AbortController signal API:
const cpAxios= require('cp-axios');
const CPromise= require('c-promise2');
const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=5s';
const abortController = new CPromise.AbortController();
const {signal} = abortController;
const chain = cpAxios(url, {signal})
.timeout(5000)
.then(response=> {
console.log(`Done: ${JSON.stringify(response.data)}`)
}, err => {
console.warn(`Request failed: ${err}`)
});
setTimeout(() => {
abortController.abort();
}, 500);
3. Using a plain axios cancelToken:
const cpAxios= require('cp-axios');
const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=5s';
const source = cpAxios.CancelToken.source();
cpAxios(url, {cancelToken: source.token})
.timeout(5000)
.then(response=> {
console.log(`Done: ${JSON.stringify(response.data)}`)
}, err => {
console.warn(`Request failed: ${err}`)
});
setTimeout(() => {
source.cancel();
}, 500);
4. Usage in a custom React hook (Live Demo):
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
/*
Note: the related network request will be aborted as well
Check out your network console
*/
function TestComponent({ url, timeout }) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
return (yield cpAxios(url).timeout(timeout)).data;
},
{ states: true, deps: [url] }
);
return (
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
<button onClick={cancel} disabled={done}>
Cancel async effect (abort request)
</button>
</div>
);
}
Update
Axios v0.22.0+ supports AbortController natively:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
Starting from v0.22.0 Axios supports AbortController to cancel requests in fetch API way:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
CancelToken deprecated
You can also cancel a request using a CancelToken.
The axios cancel token API is based on the withdrawn cancelable promises proposal.
This API is deprecated since v0.22.0 and shouldn't be used in new projects
You can create a cancel token using the CancelToken.source factory as shown below:
import {useState, useEffect} from 'react'
export function useProfileInformation({accessToken}) {
const [profileInfo, setProfileInfo] = useState(null)
useEffect(() => {
const abortController = new AbortController()
window
.fetch('https://api.example.com/v1/me', {
headers: {Authorization: `Bearer ${accessToken}`},
method: 'GET',
mode: 'cors',
signal: abortController.signal,
})
.then(res => res.json())
.then(res => setProfileInfo(res.profileInfo))
return function cancel() {
abortController.abort()
}
}, [accessToken])
return profileInfo
}
// src/app.jsx
import React from 'react'
import {useProfileInformation} from './hooks/useProfileInformation'
export function App({accessToken}) {
try {
const profileInfo = useProfileInformation({accessToken})
if (profileInfo) {
return <h1>Hey, ${profileInfo.name}!</h1>
} else {
return <h1>Loading Profile Information</h1>
}
} catch (err) {
return <h1>Failed to load profile. Error: {err.message}</h1>
}
}

SvelteKit UnhandledPromiseRejection with strange TLS Connection error

I have here a strange collection of error, for which i canno't find any solutions on the internet ( maby i'm just bad with google ).
I created a website, with the following files/folder tree:
With this setup i first got this error message:
Client network socket disconnected before secure TLS connection was established.
Which I didn't find any solution for other then reload the page.
After deciding handling the error myself i tried this:
api.js
import axios from 'axios';
import axiosRetry from 'axios-retry';
const base = process.env['API_ENDPOINT'];
axiosRetry(axios, { retries: 5 });
async function send({ path }) {
let pages = 0;
let resp;
try {
resp = await axios.get(`${base}/${path}`);
} catch (error) {
throw error;
}
if ('X-WP-TotalPages' in resp.headers) {
pages = resp.headers['X-WP-TotalPages'];
}
return {
pages: pages,
body: resp.data
};
}
export async function get(path) {
return send({ method: 'GET', path });
}
And i call it in (for example) landing.js:
import { writable } from 'svelte/store';
import { browser } from '$app/env';
import * as api from '$lib/api';
let loading = false;
let posts = [];
const list = writable({
loading,
posts
});
export default {
subscribe: list.subscribe,
async fetchNews() {
if (loading) return {};
loading = true;
list.set({ loading, posts });
let lang = 'de';
if (browser) {
lang = localStorage.getItem('lang') || 'de';
}
try {
const res = await api.get(`posts?filter[lang]=${lang}&per_page=4&_embed`);
} catch (error) {
throw error;
}
posts = await res.body;
posts.map((post) => {
if (post._embedded['wp:featuredmedia'])
post.image = post._embedded['wp:featuredmedia'][0].source_url;
else post.image = '/news/news_placeholder.png';
});
loading = false;
list.set({ loading, posts });
}
};
Now the new error is something like this:
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block, or by rejecting a
promise which was not handled with .catch(). The promise rejected with
the reason "Test".] { code: 'ERR_UNHANDLED_REJECTION' }
Maybe I'm just an idiot right, but i need really some help here!
Thanks in advance for this.
Try wrapping the whole function body in try {...}, instead of the const res = await api.get... line and observe where exactly the error happens.

axios dosen't catch error with axios-auth-refresh library

I am trying to use axios-auth-refresh library installed by npm. I stucked at problem i can't resolve. According to documentation i made code like this:
const refreshAuthLogic = (failedRequest) => {
let tokenData = JSON.parse(localStorage.getItem("REACT_TOKEN_AUTH"));
if (tokenData) {
return axios
.post(`${process.env.REACT_APP_API_URI}/Login/refresh-token`, {
departmentId: tokenData.departmentId,
jwtToken: tokenData.jwtToken,
userId: tokenData.userId,
})
.then((response) => {
console.log(response);
localStorage.setItem("REACT_TOKEN_AUTH", JSON.stringify(response.data));
failedRequest.response.config.headers["Authorization"] =
"Bearer " + response.data.jwtToken;
return Promise.resolve();
})
.catch((err) => {
console.log(`refreshed failed`);
logout();
return Promise.reject(err);
});
} else {
logout();
}
};
createAuthRefreshInterceptor(axios, refreshAuthLogic, {
statusCodes: [401],
});
Everything works almost perfect but catch never fire up so i can't logout user when refresh token expired. I get answer from api with 401 code but even if i try to put simple console log in .catch() nothing happen.
I have no idea what am i doing wrong? any ideas how to add logout() function to that code that will work?
Thanks to Sangam Rajpara, I've found a solution. You need to create a separate instance of axios.
const requestToken = axios.create();
Then use interceptors for the created instance. You can read about them on the axios page. Something like this:
requestToken.interceptors.response.use(
(res) => res,
(err) => {
// your log out action
logout();
return err;
}
);
Then, in your code instead of axios, use the instance that you created:
requestToken
.post(`${process.env.REACT_APP_API_URI}/Login/refresh-token`, {
departmentId: tokenData.departmentId, ...
You don't really need that "if" logic anymore

Axios cancel Request shows browser alert

Axios cancel Request shows browser alert which stops the execution until you click ok.
I want to cancel my requests, all my Api calls are in separate file named apiCalls.js.
Component with cancelToken.
componentDidMount() {
const CancelToken = axios.CancelToken;
// create the source
this.source = CancelToken;
}
persistCancel = (cancel) => {
this.setState({cancels: [...this.state.cancels, cancel]})
}
componentWillUnmount(){
this.state.cancels.forEach((c) => c());
}
this is my Api call from component.
getScoreCardCall({profileId, campaignIds, startDate, endDate}, (scoreCards) => {
//success
this.setState({
scoreCards,
showComcardsLoader: false
})
},this.source,this.persistCancel);
and in the apiCalls.js
export function getScoreCardCall(params,callback, source,onRequest){
axios.get(url,
{
cancelToken: new source(function executor(c) {
onRequest(c);
}),
params:{
profileId: params.profileId,
campaignId: params.campaignIds.toString(),
startDate: params.startDate,
endDate: params.endDate,
}
})
.then(res => {
if(callback != null){
if(res.data.length!=0){
callback(res.data);
}
}
})
.catch(err => {
if (axios.isCancel(err)) {
console.log(err.message);
}
})
}
Can someone please tell me why is alert showing with every request cancellation?? or what i am doing wrong?
axios#cancellationdescribes two ways to use the cancelToken. You used the first way, with source.token/source.cancel. I started out that way, and had a similar problem as yours: Once a request to a particular URL was canceled, I could never get a successful response from that URL again. I switched to the second method using an executor function and the problem went away. I guess was sharing the same cancelToken for multiple requests, which is what they say you can do with the executor function method. Anyway, maybe that would work for you too.

How to avoid sending multiple duplicate AJAX requests in axios

Is it possible to automatically throttle all requests going to a particular list of endpoints using axios? Perhaps using axios interceptor?
Currently I throttle the user action that sends the axios request, but the problem with that is that I have to write this everywhere I have a user action that results in some AJAX request. Like this
const throttledDismissNotification = throttle(dismissNotification, 1000)
const dismiss = (event: any) => {
throttledDismissNotification();
};
render() {
return (
<Button onClick={dismiss}>Dismiss Notification</Button>
)
}
This results in a lot of clutter and I was wondering if this could be automated.
Something like:
if(request.url in listOfEndpointsToThrottle && request.params in cacheOfPreviousRequestsToThisEndpoint) {
StopRequest();
}
Obviously this is pseudocode but you get the idea.
Perhaps you could try to use the Cancellation feature that axios provides.
With it, you can ensure that you don't have any two (or more, depending on your implementation) similar requests in a pending state.
Below, you will find a small simplified example of how to ensure that only the latest request is processed. You can adjust it a bit to make it function like a pool of requests
import axios, { CancelToken } from 'axios';
const pendingRequests = {};
const makeCancellable = (headers, requestId) => {
if (!requestId) {
return headers;
}
if (pendingRequests[requestId]) {
// cancel an existing request
pendingRequests[requestId].cancel();
}
const source = CancelToken.source();
const newHeaders = {
...headers,
cancelToken: source.token
};
pendingRequests[requestId] = source;
return newHeaders;
};
const request = ({
url,
method = 'GET',
headers,
id
}) => {
const requestConfig = {
url,
method,
headers: makeCancellable(headers || {}, id)
};
return axios.request(requestConfig)
.then((res) => {
delete pendingRequests[id];
return ({ data: res.data });
})
.catch((error) => {
delete pendingRequests[id];
if (axios.isCancel(error)) {
console.log(`A request to url ${url} was cancelled`); // cancelled
} else {
return handleReject(error);
}
});
};
export default request;
It's quite easy to throttle an axios request itself. The real headache is how to handle the promises that are returned from nullified requests. What is considered sane behavior when dealing with promises that are returned from a nullified axios request? Should they stay pending forever?
I don't see any perfect solution to this problem. But then I come to a solution that is kind of cheating:
What if we don't throttle the axios call, instead we throttle the actual XMLHttpRequest?
This makes things way easier, because it avoids the promise problem, and it's easier to implement. The idea is to implement a cache for recent requests, and if a new request matches a recent one, you just pull the result from cache and skip the XMLHttpRequest.
Because of the way axios interceptors work, the following snippet can be used to skip a certain XHR call conditionally:
// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
/* check the cache, if hit, then intentionally throw
* this will cause the XHR call to be skipped
* but the error is still handled by response interceptor
* we can then recover from error to the cached response
**/
if (requestCache.isCached(config)) {
const skipXHRError = new Error('skip')
skipXHRError.isSkipXHR = true
skipXHRError.request = config
throw skipXHRError
} else {
/* if not cached yet
* check if request should be throttled
* then open up the cache to wait for a response
**/
if (requestCache.shouldThrottle(config)) {
requestCache.waitForResponse(config)
}
return config;
}
});
// This should be the *first* response interceptor to add
axios.interceptors.response.use(function (response) {
requestCache.setCachedResponse(response.config, response)
return response;
}, function (error) {
/* recover from error back to normality
* but this time we use an cached response result
**/
if (error.isSkipXHR) {
return requestCache.getCachedResponse(error.request)
}
return Promise.reject(error);
});
I have a similar problem, thru my research it seems to lack a good solution. All I saw were some ad hoc solutions so I open an issue for axios, hoping someone can answer my question https://github.com/axios/axios/issues/2118
I also find this article Throttling Axios requests but I did not try the solution he suggested.
And I have a discussion related to this My implementation of debounce axios request left the promise in pending state forever, is there a better way?
I finish one, #hackape thank you for you answer, the code is as follows:
const pendings = {}
const caches = {}
const cacheUtils = {
getUniqueUrl: function (config) {
// you can set the rule based on your own requirement
return config.url + '&' + config.method
},
isCached: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
return caches[uniqueUrl] !== undefined
},
isPending: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
if (!pendings[uniqueUrl]) {
pendings[uniqueUrl] = [config]
return false
} else {
console.log(`cache url: ${uniqueUrl}`)
pendings[uniqueUrl].push(config)
return true
}
},
setCachedResponse: function (config, response) {
let uniqueUrl = this.getUniqueUrl(config)
caches[uniqueUrl] = response
if (pendings[uniqueUrl]) {
pendings[uniqueUrl].forEach(configItem => {
configItem.isFinished = true
})
}
},
getError: function(config) {
const skipXHRError = new Error('skip')
skipXHRError.isSkipXHR = true
skipXHRError.requestConfig = config
return skipXHRError
},
getCachedResponse: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
return caches[uniqueUrl]
}
}
// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
// to avoid careless bug, only the request that explicitly declares *canCache* parameter can use cache
if (config.canCache) {
if (cacheUtils.isCached(config)) {
let error = cacheUtils.getError(config)
throw error
}
if (cacheUtils.isPending(config)) {
return new Promise((resolve, reject) => {
let interval = setInterval(() => {
if(config.isFinished) {
clearInterval(interval)
let error = cacheUtils.getError(config)
reject(error)
}
}, 200)
});
} else {
// the head of cacheable requests queue, get the response by http request
return config
}
} else {
return config
}
});

Categories