axios-retry is not working for response with status 4XX - javascript

I am using axios for api calls.
Incase some failure happens or response status != 200 I need to retry the api call.
By default retry axios works for status with 5XX . But as per documentation we can override retryCondition as per our requirements.
Here is my code snippet
export const doApiFetchCall = (apiEndPoint, dataPayLoad, config, axiosObject, callType,caller,timeoutParam,retryCount) => {
let instance = undefined;
if(axiosObject === 'axios') {
instance = localAxios;
} else if(axiosObject === 'axiosProxy') {
instance = localAxiosProxy;
} else if (axiosObject === 'axiosProxyJira') {
instance = localAxiosProxyJira;
}
let restOptions = {
url: apiEndPoint,
method: callType,
timeout: timeoutParam || 20000, // timeout in ms
headers:config.headers||null,
raxConfig: {
retry: retryCount || 0, // number of retry when facing 4xx or 5xx
instance: instance,
retryCondition: () => true,
onRetryAttempt: err => {
let tempError = Object.assign({}, err)//{...err}
const cfg = rax.getConfig(err);
delete tempError.config;
delete tempError.request;
},
noResponseRetries: 3, // number of retry when facing connection error
httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST', 'PATCH'],
retryDelay: 3000,
backoffType: 'static'
}
};
var caller_id = caller||'';
if (dataPayLoad) restOptions = {...restOptions, data: dataPayLoad};
return new Promise((resolve, reject) => {
instance(restOptions)
.then(response => {
logger.info(caller_id, '[API_CALL_SUCCESS] API call has succeeded');
if (response) {
const {status, data} = response;
logger.info(caller_id, '[API_CALL_SUCCESS] API call Status Code: [' + status + ']');
try {
if (status === 200 || status === 201) {
resolve(response);
} else {
reject(null);
}
} catch (jsonParseError) {
reject(jsonParseError);
}
} else {
resolve(null);
}
})
.catch(error => {
const {response, request, message, config} = error;
reject(error);
});
I have overridden retryCondition I am not sure if its done in correct way.
Can someone please let me know what wrong I am doing ?

Got the fix .
I over ride statusCodesToRetry property.

Related

Return Response When First request failed And Try In Second Request

I try to explain the problem.in App.js I have Function getUser .when call this function.in first request get 401 error . For this reason in axios.interceptors.response I receive error 401.At this time, I receive a token and repeat my request again.And it is done successfully.But not return response in Function getUser.
I have hook for authentication and send request.
import React from "react";
import axios from "axios";
const API_URL = "http://127.0.0.1:4000/api/";
function useJWT() {
axios.interceptors.request.use(
(request) => {
request.headers.common["Accept"] = "application/json";
console.log("request Send ");
return request;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
console.log("answer = ", response);
return response;
},
(error) => {
if (error?.response?.status) {
switch (error.response.status) {
case 401:
refreshToken().then((responseTwo) => {
return
sendPostRequest(
error.response.config.url
.split("/")
.findLast((item) => true)
.toString(),
error.response.config.data
);
});
break;
case 500:
// Actions for Error 500
throw error;
default:
console.error("from hook interceptor => ", error);
throw error;
}
} else {
// Occurs for axios error.message = 'Network Error'
throw error;
}
}
);
const refreshToken = () => {
const token = localStorage.getItem("refresh");
return axios
.post(API_URL + "token", {
token,
})
.then((response) => {
if (response.data.access) {
localStorage.setItem("access", response.data.access);
}
if (response.data.refresh) {
localStorage.setItem("refresh", response.data.refresh);
}
return response.data;
});
};
function login(email, password) {
return axios
.post(API_URL + "login", {
email,
password,
})
.then((response) => {
if (response.data.access) {
localStorage.setItem("access", response.data.access);
}
if (response.data.refresh) {
localStorage.setItem("refresh", response.data.refresh);
}
return response.data;
});
}
const sendPostRequest = (url, data) => {
console.log(300);
const token = localStorage.getItem("access");
axios.defaults.headers.common["jwt"] = token;
return axios.post(API_URL + url, {
data,
});
};
const logout = () => {
const token = localStorage.getItem("refresh");
return axios
.delete(API_URL + "logout", {
token,
})
.then((response) => {
localStorage.removeItem("access");
localStorage.removeItem("refresh");
});
};
return {
login,
logout,
refreshToken,
sendPostRequest,
};
}
export default useJWT;
In App.js ,I want to repeat the same request again if a 401 error is issued when I read the user information.
The request is successfully repeated but does not return the value.
When first request fail response is return equals null . and in catch when receive 401 error i am send second request but not return response.
I send request below code .
const getUser = () => {
console.log(12);
return sendPostRequest("user");
};
useEffect(() => {
let token = localStorage.getItem("access");
console.log("token = ", token);
if (token != null) {
//Here I have done simulation for 401 error
localStorage.setItem("access", "");
getUser()
.then((response) => {
console.log("response 1= ", response);
})
.catch((exception) => {
console.log("exception = ", exception.toString());
})
.then((response) => {
console.log("response 2= ", response);
});
} else {
navigate("/login");
}
}, []);
Best regards.
I didn't fully understand what exactly you want to do here.
But if you are looking to retry when 401 happens, you could use axios-retry to do it for you.
I'll pass the basics, but you can look more into what this does.
// First you need to create an axios instance
const axiosClient = axios.create({
baseURL: 'API_URL',
// not needed
timeout: 30000
});
// Then you need to add this to the axiosRetry lib
axiosRetry(axiosClient, {
retries: 3,
// Doesn't need to be this, it can be a number in ms
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
// You could do this way or try to implement your own
return error.response.status > 400
// something like this works too.
// error.response.status === 401 || error.response.status >= 500;
}
});
Just like in your code, we need to use interceptors if you want to avoid breaking your page, otherwise you can use try catch to catch any errors that may happen in a request.
// It could be something like this, like I said, it's not really needed.
axiosClient.interceptors.response.use(
(success) => success,
(err) => err
);
And finally, you could use the axiosClient directly since it now has your API_URL, calling it like this axiosClient.post('/user').
More or less that's it, you should just debug this code and see what is causing the return value to be null.
I would change these then/catch to be an async/await function, it would be more readable making your debugging easier.
axios-retry example if you didn't understand my explanation.
I find anwser for this question.
When error 401 occurs then create new Promise
I Wrote this code.
case 401:
return new Promise((resolve, reject) => {
refreshToken().then((responseTwo) => {
resolve(
sendPostRequest(
error.response.config.url
.split("/")
.findLast((item) => true)
.toString(),
error.response.config.data
)
);
});
});

How to Return a custom axios response based on response's error code

I'm trying to make a global error handling in my vue application. I have a api.service.js file that includes my axios and, creates and my get,post functions:
/**
* Service to call HTTP request via Axios
*/
const ApiService = {
init(apiBaseUrl) {
Vue.use(VueAxios, axios);
Vue.axios.defaults.baseURL = apiBaseUrl;
},
/**
* Set the default HTTP request headers
*/
setHeader() {
Vue.axios.defaults.headers.common[
"Authorization"
] = `Bearer ${JwtService.getToken()}`;
},
setHeaderwToken(token) {
Vue.axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
},
/**
* Send the GET HTTP request
* #param resource
* #param slug
* #returns {*}
*/
get(resource, slug = "") {
var myBlob = new Blob([], {type:'text/plain'});
var init = { status: 200, statusText: "" };
var myResponse = new Response(myBlob, init);
return Vue.axios.get(`${resource}/${slug}`)
.catch((error) => {
if (error.response.status == 401) {
//401 response
if (resource != "CheckToken") {
// request isNot checktoken & 401 response, check if token is valid?
Vue.axios
.get("CheckToken")
.then((CheckTokenResponse) => {
console.log("CheckToken response");
if (CheckTokenResponse.data == "OK") {
//token valid + 401 response
init = { status: 401, statusText: "noAuthorityValid" };
myResponse = new Response(myBlob, init);
console.log(CheckTokenResponse);
console.log("//token valid + 401 response");
console.log(myResponse);
return myResponse;
} else {
init = { status: 401, statusText: "noTokenValid" };
myResponse = new Response(myBlob, init);
console.log(CheckTokenResponse);
console.log("//token NOT valid + 401 response");
return myResponse;
}
})
.catch(() => {
init = { status: 401, statusText: "noTokenValid" };
myResponse = new Response(myBlob, init);
return myResponse;
});
} else {
//request is CheckToken + 401 response
init = { status: 401, statusText: "noTokenValid" };
myResponse = new Response(myBlob, init);
console.log(error);
console.log("//request is CheckToken + 401 response");
return myResponse;
}
} else {
// != 401 response
console.log(error);
console.log("!= 401 response");
return error;
}
});
},
};
export default ApiService;
In my Vue component, I'm calling my ApiService:
ApiService.get("MyFunction")
.then((response) => {
console.log("MyFunction " + response);
.catch((error) => {
console.log("MyFunction " + error);
});
},
I tried to create a custom response (myResponse) and return it but it returns as undefined
(I guess that's a wrong approach)
What i want to achieve is,
when a function is called and return an error code from api,
(500, 401, 404..)
i want to catch it,
and if it's 401, then i want to call "CheckToken" function and then if, CheckToken returns "OK" i want to return "noAuthorityValid" (means token is valid but that function is unauthorized.), CheckToken is not OK, then i want to return noTokenValid and i want to do it in my vue component where i call my function:
ApiService.get("MyFunction")
.then((response) => {
console.log("MyFunction " + response);
// if (response.statusText == noAuthorityValid)
{
// show snackbar("you are not authorized for this function")
}
})
.catch((error) => {
console.log("MyFunction " + error);
});
},
I couldn't do it with api.service.js so i created a walk-around.
I imported axios in every component i need an axios call;
import axios from "axios";
then i used axios like this:
axios({
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
url: "MyFunction",
method: "get",
})
.then((response) => {....}
And then, in my top component's (top parent) created function, i used axios.interceptors.response like this:
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
this.handleError(error);
return Promise.reject(error);
}
);
and this is my handleError function:
handleError(error) {
if (error.response.status == 401) {
if (error.response.data.includes("expiredToken")) {
this.showSnackbar("Token is expired");
setTimeout(() => {
if (!window.location.href.includes("login")) {
this.$router.push({ name: "login" }).then(() => {
this.$store.dispatch(LOGOUT); //PURGES user data,
});
}
}, 2000);
} else if (
error.response.data.includes(
"UnauthorizedFunction"
)
) {
this.showSnackbar("You are not authorized for this function ");
} else {
this.showSnackbar("Error occured.");
}
} else {
this.showSnackbar("Error occured.");
}
}
This stupid problem took my 2.5 days..

How to set two interceptors in axios

I have two functions that using axios post information to different APIs I created with node and express. Both of them have an interceptor as I get a response from by backend with messages, errors, and other information. Yet when I post the to the second url ("/users/login") the first interceptor still fires off (in the addUser instead of the findUser function) even though it is not in the same function. How do I fix this?
async function addUser(user) {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const interceptorResponse = axios.interceptors.response.use(
(response) => {
if (typeof response.data === "object") {
let success = response.data.registerSuccess;
let errors = response.data.errors;
let data = response.data.data;
let message = response.data.message;
setData(() => {
return { ...data, errors, registerSuccess: success, message };
});
}
return response;
}
);
await axios.post("/users/register", user, config);
axios.interceptors.request.eject(interceptorResponse);
} catch (err) {}
}
async function findUser(user) {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
axios.interceptors.response.use((response) => {
console.log(response);
if (typeof response.data === "object") {
let loginSuccess = response.data.data.loginSuccess;
let message = response.data.message;
console.log(response.data);
setData(() => {
return { ...data, loginSuccess, message };
});
}
return response;
});
await axios.post("/users/login", user, config);
} catch (error) {}
}

Add both settimeout and timeout to in the array loop

I am looping the array using bluebird map method and then using each row as a payload to my apicall function. Everything works perfectly but I want to incorporate timeout method when response from the api takes more than 10 seconds and also settimeout method to delay 2 seconds after each api call. Please let me know how can I acheive this. I am fine without using bluebird. Thanks in advance.
handleSubmit = () => {
Promise.map(this.props.data, row => {
return apiCall(api, row).then((response) => {
if(response){
console.log(response)
} else{
console.log("failed")
}
})
}, { concurrency: 1 } )
}
apiCall: (api, input ) => {
switch (process.env.NODE_ENV) {
case 'production': { // Production environment
return new Promise((resolve) => {
window.runApi(api, input, (response) => {
if (typeof response === 'string') {
const jsonResponse = JSON.parse(response);
resolve(jsonResponse);
} else {
resolve(response);
}
});
});
}
default: {
const requestBody = input;
if (input !== "") {
requestBody.username = "user";
requestBody.password = "password";
}
const requestUrl = `api`;
return fetch(requestUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
})
.then(res => res.json());
}
}
}

React-native NetInfo with promises

I have a modified code in react-native for fetching data with server, that works fine. I want to add NetInfo to always check before fetching if telephone has connection to internet. Is it posible inside promise? How to connect this async function to my code?
'use strict';
var MAX_WAITING_TIME = 30000
var processStatus = function (response) {
// status "0" to handle local files fetching (e.g. Cordova/Phonegap etc.)
if (response.status === 200 || response.status === 0 || response.status === 201 || response.status === 422 || response.status === 302 ) {
return Promise.resolve(response)
} else if(response.status === 413) {
return Promise.reject(alert(____mobile.connection_error.large_file))
} else {
//return Promise.reject(alert("Process status: "+JSON.stringify(response )))
return Promise.reject(alert(____mobile.connection_error.top));
console.log("Process status: "+JSON.stringify(response ));
}
};
var parseJson = function (response) {
return response.json();
};
var getWrappedPromise = function () {
var wrappedPromise = {},
promise = new Promise(function (resolve, reject) {
wrappedPromise.resolve = resolve;
wrappedPromise.reject = reject;
});
wrappedPromise.then = promise.then.bind(promise);
wrappedPromise.catch = promise.catch.bind(promise);
wrappedPromise.promise = promise;// e.g. if you want to provide somewhere only promise, without .resolve/.reject/.catch methods
return wrappedPromise;
};
/* #returns {wrapped Promise} with .resolve/.reject/.catch methods */
var getWrappedFetch = function () {
var wrappedPromise = getWrappedPromise();
var args = Array.prototype.slice.call(arguments);// arguments to Array
fetch.apply(null, args)// calling original fetch() method
.then(function (response) {
wrappedPromise.resolve(response);
}, function (error) {
// wrappedPromise.reject(alert("Fetch status: " + error));
wrappedPromise.reject(____mobile.connection_error.top);
console.log("Fetch status: " + error);
})
.catch(function (error) {
wrappedPromise.catch(error);
});
return wrappedPromise;
};
/**
* Fetch JSON by url
* #param { {
* url: {String},
* [cacheBusting]: {Boolean}
* } } params
* #returns {Promise}
*/
var postJSON = function (params) {
var headers1 = {}
if (params.json){
headers1 = {
'Accept': 'application/json',
'Content-Type': 'application/json'}
}
if (params.headersIn){
headers1 = params.headersIn
}
var methodTmp = 'POST'
if (params.methodIn) {
methodTmp = params.methodIn
}
console.log(methodTmp)
var wrappedFetch = getWrappedFetch(
params.cacheBusting ? params.url + '?' + new Date().getTime() : params.url,
{
method: methodTmp,//'POST',// optional, "GET" is default value
headers: headers1,
body: params.send_data
});
var timeoutId = setTimeout(function () {
wrappedFetch.reject(alert(____mobile.connection_error.timeout, ____mobile.connection_error.check_connection));// reject on timeout
}, MAX_WAITING_TIME);
return wrappedFetch.promise// getting clear promise from wrapped
.then(function (response) {
clearTimeout(timeoutId);
return response;
})
.then(processStatus)
.then(parseJson);
};
module.exports = postJSON;
What would be the bast way to implement: NetInfo.isConnected.fetch() so fetched would only worked when there is internet connection?
EDIT:
I want to use:
NetInfo.isConnected.fetch()
Yeah I have to rewrite this code, not to use getWrappedPromise and now I think is good time for it.
EDIT2: Ok I refactored this code fragment, hope its better. Any comments welcome. I tested and I'm not sure if I still need this NetInfo.isConnected.fetch(). Now there is no errors where there is no connection or am I missing something?
New code:
var processStatus = function (response) {
if (response == undefined) {
return null
}
// status "0" to handle local files fetching (e.g. Cordova/Phonegap etc.)
if (response.status === 200 || response.status === 0 || response.status === 201 || response.status === 422 || response.status === 302 ) {
return Promise.resolve(response)
} else if(response.status === 413) {
return Promise.reject(alert(____mobile.connection_error.large_file))
} else {
//return Promise.reject(alert("Process status: "+JSON.stringify(response )))
console.log("Process status: "+JSON.stringify(response ));
return Promise.reject(alert(____mobile.connection_error.top));
}
};
var parseJson = function (response) {
if (response == undefined) {
return null
}
return response.json();
};
var postJSON = function (params) {
var headers1 = {}
if (params.json){
headers1 = {
'Accept': 'application/json',
'Content-Type': 'application/json'}
}
if (params.headersIn){
headers1 = params.headersIn
}
var methodTmp = 'POST'
if (params.methodIn) {
methodTmp = params.methodIn
}
console.log(methodTmp)
var fetchPromise = fetch(params.cacheBusting ? params.url + '?' + new Date().getTime() : params.url,
{
method: methodTmp,//'POST',// optional, "GET" is default value
headers: headers1,
body: params.send_data
})// calling original fetch() method
.then(function (response) {
return response;
}, function (error) {
console.log("Fetch status: " + error);
return fetch
}).then(processStatus)
.then(parseJson);
// timeoutId = setTimeout(function () {
// wrappedFetch.reject(alert(____mobile.connection_error.timeout, ____mobile.connection_error.check_connection));// reject on timeout
// }, MAX_WAITING_TIME);
return fetchPromise
};

Categories