Axios interceptors runs more than once - javascript

I have this interceptors that logs the user out if unauthorized. I do, however, get more than a 401 response, and the interceptors does therefore run for as many times as the responses I get (4). Is there a way to make it run only for the first one?
This is my code:
api.interceptors.response.use(
(response) => response,
(err) => {
if (err.response.status === 401 && isLoggedIn) {
api
.delete("auth/sign_out")
.then((resp) => {
clearLocalStorage();
})
.catch((err) => {
clearLocalStorage();
});
} else {
return Promise.reject(err);
}
return err;
}
);

You might want something like this to "lock out" the possible re-entrant calls:
let isLoggingOut = false; // global
// ...
api.interceptors.response.use(
(response) => response,
async (err) => {
if (err.response.status === 401 && isLoggedIn) {
if(!isLoggingOut) {
isLoggingOut = true; // disallow re-entrant calls
try {
await api.delete('auth/sign_out');
} catch (deletionError) {
// throw errors away
} finally {
clearLocalStorage();
isLoggingOut = false;
isLoggedIn = false; // if the variable is assignable
}
}
}
return err;
},
);

Related

Problem in implementing Axios.get in Reactjs

I am new to react and I want to make an Axios.get() request based on a function parameter. This is what I tried.
const mentorName = (value) => {
try {
Axios.get(
`${BASE_URL}/api/v1/consultations/${value}`
).then(res => {
if (res.status !== 200 || res.data.status !== "success") {
console.log(res)
return
}
})
} catch (error) {
console.log(error)
}
}
But It didn't work as res was not printed in console. What is wrong in this?
The code that worked fine is:
const mentorName = (value) => {
try {
const res = Axios.get(
`${BASE_URL}/api/v1/consultations/${value}`
)
if (res.status !== 200 || res.data.status !== "success") {
console.log(res)
return
}
} catch (error) {
console.log(error)
}
}
The below code worked fine but returns information wrapped in a promise. How to access it now because res.data is not a valid property.
Can you try this with async/await.
import axios from 'axios';
const mentorName = async value => {
try {
const res = await axios.get(`${BASE_URL}/api/v1/consultations/${value}`);
console.log(res);
} catch (error) {
console.log(error);
}
};
In the console.log inside try block you can check for the api response.
const mentorName = (value) => {
try {
Axios.get(
`${BASE_URL}/api/v1/consultations/${value}`
).then(res => {
if (res.status !== 200 || res.data.status !== "success") {
console.log(res)
return
}
})
} catch (error) {
console.log(error)
}
}
above code doesn't print because of the if condition, it is likely that the status is going to be 200 most of the time and anything apart from 200 would drill down to catch block
the reason it is printing promise in below code is because, it is a promise waiting to be fulfilled and the comparison / condition you have put up is very much fine because res is a promise and res.status is undefined
const mentorName = (value) => {
try {
const res = Axios.get(
`${BASE_URL}/api/v1/consultations/${value}`
)
if (res.status !== 200 || res.data.status !== "success") {
console.log(res)
return
}
} catch (error) {
console.log(error)
}
}
tweak the code to include an else block and you can always see something printed in console
const mentorName = (value) => {
try {
Axios.get(
`${BASE_URL}/api/v1/consultations/${value}`
).then(res => {
if (res.status !== 200 || res.data.status !== "success") {
console.log(res)
return
} else {
console.log(res);
}
})
} catch (error) {
console.log(error)
}
}
I do not recommend using async/await due to one single and pressing reason, that the UI thread is put on hold until the async call is resolved. just to make it look like a synchronous call. stick on to the promise way.

How to return a promise from a boolean check inline?

I'm new to promise syntax. Previously I had code like this, and requests would return a zipfile:
// in first file
exports.requireSubscription = function(req) {
if (feature_is_available) {
return Promise.resolve();
}
else {
return Promise.reject(new Error("You need to upgrade your account to access this feature."));
}
};
//from the npm package https://www.npmjs.com/package/archiver
const archiver = require("archiver");
utils.requireSubscription(req)
.then(() => getPage(req, res, "view"))
.then(function(page) {
const zip = archiver.create("zip", {});
// ...
zip.finalize();
}).catch(utils.fail(req, res));
Now I want to remove the separate function for requireSubscription, and use a single file with a check inline.
I've tried this:
if (feature_is_available) {
getPage(req, res, "view"))
.then(function(page) {
const zip = archiver.create("zip", {});
// ...
zip.finalize();
});
} else {
utils.fail(req, res);
}
However, the request is hanging. I think perhaps I'm not returning a promise when I should be returning one - previously requireSubscription returned a promise, now my inline check does not.
How can I rewrite this to return the right thing?
Update: here is the utils.fail function:
exports.fail = function(req, res) {
return function(error) {
if (error instanceof acl.PermissionDeniedError) {
return res.status(403).render("error_nothing_here.html", { user: req.user, error: error });
}
else if (error instanceof errors.NotFoundError) {
return res.status(404).render("error_nothing_here.html", { user: req.user, error: error });
}
res.status(500).render("internal_error.html", { "error": error });
};
};
You can use a ternary operator to inline the body of the function as a single expression:
(feature_is_available
? Promise.resolve()
: Promise.reject(new Error("You need to upgrade your account to access this feature."))
).then(() =>
getPage(req, res, "view")
).then(page => {
const zip = archiver.create("zip", {});
// ...
zip.finalize();
}).catch(utils.fail(req, res));
The problem with your version that the promise chain had no catch handler attached, and that you didn't call the function that was created by fail() in the else branch. You would need to write
if (feature_is_available) {
getPage(req, res, "view"))
.then(page => {
const zip = archiver.create("zip", {});
// ...
zip.finalize();
})
.catch(utils.fail(req, res));
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
} else {
utils.fail(req, res)(new Error("You need to upgrade your account to access this feature."));
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

After post request, continue checking for completion | Node.js

I'm making a post request with a good amount of data that will take about a minute to finish saving. The hosting service I'm using for this will time out requests after 5 seconds, so I need to set this up to periodically check if the data saving is complete to give a final update.
I'm using request-promise, and have looked at both setTimeout and setInterval approaches. In my latest attempt (below) I'm using a setTimeout approach, but my second then keeps being called pretty much immediately. I want this to hang out in the first then stage until it's checked a bunch of times (24 here) or actually finished.
I might have a totally wrong approach here, but I'm not finding examples of the thing I'm trying to reference. Any direction to a good example of this or where I'm going wrong would be greatly appreciated.
const request = require('request-promise');
function checkFiles () {
return request({
uri: `${process.env.ROOT_URL}/api/v1/get/file-processing`,
method: 'GET',
json: true
})
.then(res => { return res; })
.catch(err => { return err; });
}
async function init () {
const filesPostOptions = {/* request options */};
await request(filesPostOptions)
.then(async status => { // THEN #1
if (status.status === 201) {
return status;
}
let checks = 0;
const checkIt = function() {
checks++;
checkFiles()
.then(res => {
if (res.status === 201 || checks > 24) {
status = res;
return status;
} else {
setTimeout(checkIt, 5000);
}
})
.catch(err => {
console.error(err);
});
};
checkIt();
})
.then(status => { // THEN #2
if (!status.status) {
throw Error('Post request timed out.');
}
return status;
})
.catch(err => {
err = err.error ? err.error : err;
console.error(err);
});
}
The post response will deliver a response with a status property (the status code) and a message property.
You need to control the return in "THEN #" by adding a Promise:
.then(async status => { // THEN #1
return new Promise((resolve, reject) => { // <---- prevent an immediate return
if (status.status === 201) {
return resolve(status);
}
let checks = 0;
const checkIt = function() {
checks++;
checkFiles()
.then(res => {
if (res.status === 201 || checks > 24) {
status = res;
resolve(status);
} else {
setTimeout(checkIt, 1000);
}
})
.catch(err => reject(err));
};
checkIt();
})
})

Returning new Error appears in then not catch function?

I have a login function which returns an Error when a login attempt fails:
export const login = async () => {
try {
// Code to get res
if (res.status === 200) {
document.cookie = `userToken=${data.token}`;
} else if (res.status === 400) {
return new Error("Oh noo!!!");
}
} catch (err) {
alert('err!! ', err);
}
};
The login form calls it with this:
submit = async event => {
login()
.then(res => {
console.log('res ', res);
})
.catch(err => {
console.log('err ', err);
});
};
When res.status is 400 and an Error is returned it appears in the then function not the catch function. How can I instead make it appear in the catch function? I assume this is best practice as the login attempt has failed.
When the interpreter sees an expresssion after return, that expression will be returned as soon as it's evaluated - even if it's an error, it won't go into the catch block. Declaring an error by itself won't cause a problem either - you need to actually throw it:
} else if (res.status === 400) {
throw new Error("Oh noo!!!");
}
If you want the submit function to handle the error as well, then don't put the try / catch in login, because then the async function call will resolve rather than be rejected - instead, just let login throw, while submit handles the catch:
export const login = async () => {
if (res.status === 200) {
document.cookie = `userToken=${data.token}`;
} else if (res.status === 400) {
throw new Error("Oh noo!!!");
}
};
You should throw the error instead of returning it, throw ... raises an exception in the current code block and causes it to exit, or to flow to next catch statement if raised in a try block.
export const login = async () => {
try {
// Code to get res
if (res.status === 200) {
document.cookie = `userToken=${data.token}`;
} else if (res.status === 400) {
throw Error("Oh noo!!!");
}
} catch (err) {
alert('err!! ', err);
}
};

How to return promise up the chain after recursion

I am making a request which returns paged data, so I want to call a function recursively until I have all of the pages of data, like so:
router.get("/", (req, res) => {
doRequest(1, [])
.then(result => {
res.status(200).send(result);
})
.catch(error => {
console.error(error);
res.status(500).send("Error from request");
});
});
function doRequest(page, list){
let options = {
uri : "www.example.com",
qs : { "page" : page }
};
request(options)
.then((results) => {
list.push(results);
if(page === 2){
return Promise.resolve(list);
} else {
return doRequest(++page, list);
}
})
.catch((error) => {
return Promise.reject(error);
});
}
My route is returning immediately with Cannot read property 'then' of undefined, so doRequest() is apparently returning undefined right away, rather than returning the list when it is ready. I'm new to promises so I'm sure I'm missing something pretty simple.
Change the doRequest function to
function doRequest(page, list){
let options = {
uri : "www.example.com",
qs : { "page" : page }
};
return request(options)
.then((results) => {
list.push(results);
if(page === 2){
return Promise.resolve(list);
} else {
return doRequest(++page, list);
}
})
.catch((error) => {
return Promise.reject(error);
});
}
so it can return the request, its actually returns nothing (undefined)
Indeed doRequest is returning 'undefined' since the function does not return anything.
The return statements that you have there belong to the predicates of the 'then' and 'catch', but you have none in the function doRequest.
You should return the result of 'request' which should be a promise, so your code should look like this
function doRequest(page, list){
let options = {
uri : "www.example.com",
qs : { "page" : page }
};
return request(options)
.then((results) => {
list.push(results);
if(page === 2){
return Promise.resolve(list);
} else {
return doRequest(++page, list);
}
})
.catch((error) => {
return Promise.reject(error);
});
}
besides that I don't think is a good approach to handle this recursively, but this is probably another discussion.

Categories