Multiple, dependent API calls in try/catch block? (NodeJS) - javascript

I am wondering how to best handle multiple, dependent API calls in nodejs with a try catch block. Say I need to send a request, wait for the response, then use the response in a subsequent API request to a different server and so on. I initially tried to separate these into several try/catch blocks, but then realised that if I am dependent on the response, I cannot really split subsequent requests apart.
Here is an example of what I mean:
try {
const token = await getAuth0Token();
const userExistsInAuth0 = await doesUserExist({
email,
token
});
if (!userExistsInAuth0) {
return res.status(500).json({
//...
});
}
// Create User
let createUserStatus = await createUser({
//....
});
if (createUserStatus == 'error') {
return res.status(200).json({
//...
});
}
const payment = await stripe.paymentIntents.create({
//...
});
return res.status(200).json({ //Success
//...
});
} catch (error) {
return res.status(500).json({ //Error
//...
});
}
Is this a recommended way to go about this? I am worried it will very soon get very messy, but I don't understand how I could break things apart.

You could define custom Error-classes and throw instances of these classes in your services. Then you handle those errors inside your already existing catch block. E.g:
class UserNotFound extends Error { ... }
class CreateUserError extends Error { ... }
// etc.
Then in your catch block you can use instanceof to determine the error:
try {
const token = await getAuth0Token();
// doesUserExist now throws an UserNotFound error if the user does not exist yet
const userExistsInAuth0 = await doesUserExist({
email,
token
});
// Create User
let createUserStatus = await createUser({
//....
});
const payment = await stripe.paymentIntents.create({
//...
});
return res.status(200).json({ //Success
//...
});
} catch (error) {
if (error instance of UserNotFound) { // handle UserNotFound error
} else if(error instance of CreateUserError) { // handle CreateUserError error
} else {
// handle any unhandled error
return res.status(500).json({ //Error
//...
});
}
}

Related

Is it correct to throw an Error inside the catch block?

I'm writing a middleware that has a services layer and a controller layer. In order to transmit a clear error message to the user, I catch any errors in the services layer and then throw a new Error with a personalized message, like this:
// services.js
async getOneRecord(id) {
const url = this.url + `/${id}`;
return await axios.get(url).then((res) => res.data);
}
// UserServices.js
async getTaxValue(id) {
try {
const user = await this.getOneRecord(id);
return Number(user.tax) / 100;
} catch (error) {
throw new Error(`User ID not found: ${id}`);
}
}
// UserController.js
static async getUserTax(req, res) {
const { userId } = req.params;
try {
const user = await userServices.getTaxValue(userId);
return res.status(200).json(user);
} catch (error) {
return res.status(404).json(error.message);
}
}
I would like to find out if this is the right way to treat errors or if I'm doing something wrong.
Yes, it's correct and normal to throw new errors in catch blocks.
However, it is not a good practice to ignore the error that you caught. There are a lot of things that can go wrong in getTaxValue, and I would argue that most of them should not result in an "id not found" error. So be very explicit about the error that you expect, test for it with a condition, and rethrow everything else unchanged. Also set the .cause of errors.
In your case, that might be (handling only 404 errors from the user record endpoint):
class NotFoundError extends Error {}
async getTaxValue(id) {
try {
const user = await this.getOneRecord(id);
return Number(user.tax) / 100;
} catch (error) {
if (error.response && error.response.status == 404) { // see AxiosError
throw new NotFoundError(`User ID not found: ${id}`, {cause: error});
}
throw error;
}
}
// UserController.js
async function getUserTax(req, res) {
const { userId } = req.params;
try {
const user = await userServices.getTaxValue(userId);
return res.status(200).json(user);
} catch (error) {
return res.status(error instanceof NotFoundError ? 404 : 500).json(error.message);
}
}

Mock a thrown exception

Is there a way to mock what a thrown exception in the try catch block? I want to test my function that the url string in error.message is indeed replaced with replacedURL string. How can I easily test it or mock the thrown exception to an object with this URL.
async function getUsers() {
try {
const response = await fetch('/api/users');
if (response.status >= 400) {
console.error('Could not fetch users');
return;
}
const users = response.json();
return users;
} catch (error) {
let message = error.message;
if (message) {
message = message.replace(/https:\/\/some.url.com /g, 'replacedURL');
delete error.message;
}
console.error('error', error);
}
}
Here's my test
test('url in error message is replaced', () => {
expect(getUsers()).toThrow('replacedURL');
})
You might want to actually throw an error in your function for it to be caught by toThrow.
Also, I'd recommend that your test doesn't actually tries fetching from the API (unless you're sure that's what you want it to do). Instead, consider using a custom caller (which can extend fetch) which you can then mock the response for, like const caller = jest.fn().mockRejectedValue({ message: 'https:://some.url.com' });

How to use a conditional validation after Axios call in Vue app

I have a Vue application where I make a POST request to my backend. I am now trying to call a validation method after the response from my backend returned back an error to my frontend. But for some reason my code is not executed:
UPDATED QUESTION CODE:
validateFormInput(){
this.$refs.form.validate()
},
saveSelectionVoter() {
var pageURL = window.location.href;
var lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
this.votersSelectArray.voterAvailableTimes = [...this.votersSelectArray.voterAvailableTimes, ...this.selected]
console.log(JSON.stringify( this.votersSelectArray))
axios.post("http://localhost:8080/api/votercontroller/",
this.votersSelectArray,
{
params: {
meetingName: lastURLSegment,
}
},
).then(function(response){
})
.catch(function (error){
this.validateFormInput()
console.log(error)
})
this.selected = []
},
This causes a new error:
TypeError: Cannot read property 'validateFormInput' of undefined
always have a catch to see the error return
axios return you a promise so it captures the error if there is any
axios.post('url')
.then((res) => {
// do somthing
}).catch(err){
console.log(err)
}
You can either use the callback method to catch the response/error or use the Promise way, which is my favorite because of scope and readability.
You start by declaring your function with async
async saveSelectionVoter() {
Then you use a try/catch block to handle the response/error:
try{
const response = await axios.post(url, params)
// handle response here
} catch (error) {
// handle error here
}

Handling network errors with axios and Twilio

I have an application that uses axios for it's ajax requests. When a user experiences a network issue (for example, their wifi goes out and they no longer have an internet connection while on my application), I want to make sure that only the first axios request is made, and if I detect there is a network issue, to not attempt any more requests, but instead to retry the same request until successful.
My application performs many requests, including a request every 2.5 seconds (in this example, getData). It also establishes a Twilio connection when the application initializes (it executes twilio() on initialization).
When a connection is lost, the following happens:
getData fails, resulting in a console message of this is a network error.
TwilioDevice.offline is executed. This results in two error messages: first a this is a network error. message (error message #2) when TwilioDevice.offline tries fetchToken(), and then a received an error. message (error message #3) after the fetchToken() fails.
Given #'s 1 and 2, how can I make sure that:
If I experience a network error, I only receive one error message instead of 3 saying that "there was a network error"
My app detects that there is a network error, then tries to re-establish a connection, then, if successful, resumes fetching data, Twilio tokens, etc.
Thanks! Code is below.
example code:
const getData = async () => {
try {
const response = await axios.get('api/data');
return response.data;
} catch (error) {
handleError(error);
}
};
const fetchToken = async () => {
try {
const data = await axios.get('api/twilio-token');
return data.token;
} catch (error) {
return handleError(error);
}
};
const handleError = error => {
if (!error.response) {
console.log("this is a network error.");
} else {
console.log("received an error.");
}
};
twilio.js:
import { Device as TwilioDevice } from 'twilio-client';
const registerEvents = () => {
TwilioDevice.ready(() => {
console.log('Twilio.Device is now ready for connections');
});
TwilioDevice.connect((conn) => {
console.log(`Connecting call with id ${conn.parameters.CallSid}`);
// code to start call
conn.disconnect((connection) => {
console.log(`Disconnecting call with id ${connection.parameters.CallSid}`);
// code to end call
});
});
TwilioDevice.error((error) => {
console.log("Twilio Error:");
console.log(error);
});
TwilioDevice.offline(async () => {
try {
const newTwilioToken = await fetchToken(); // error message #2
return TwilioDevice.setup(newTwilioToken);
} catch (error) {
return handleError(error); // error message #3
}
});
};
export const twilio = async () => {
try {
registerEvents();
const twilioToken = await fetchToken();
TwilioDevice.setup(twilioToken);
} catch (error) {
return handleError(error);
}
};
I would recommend making your fetchToken and getData methods to throw errors rather than handling it themselves so that they can be handled by their outer functions.
Something like,
const getData = async () => {
try {
const response = await axios.get('api/data');
return response.data;
} catch (error) {
throw (error);
}
};
const fetchToken = async () => {
try {
const data = await axios.get('api/twilio-token');
return data.token;
} catch (error) {
throw (error);
}
};
So that when you call twilio() that function can handle the error like retrying etc.

this await throwing unexpected token error

I have a simple async function. It just sends a request and returns the data:
export const updatePanorama = async ({ commit }, payload) => {
const urlEnd = '/v1/pano/update'
const type = 'post'
const resp = await api.asyncRequest(urlEnd, type, payload)
commit('SET_PANORAMA', resp.data)
return resp
}
And this is how I'm using the function:
handleUpdatePanorama (panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
The problem is, the code after catch runs if there's an error inside then. But this way I don't know whether the catch error is an request error or and error triggered by the code inside then.
I'm trying try and catch to solve that problem:
handleUpdatePanorama (panorama) {
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
} catch (err) {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
However, I get an unexpected token error in this line: await this.updatePanorama(payload)
What am I doing wrong?
The problem is, the code after catch runs if there's an error inside then
The solution for that is to not use catch, but the second then parameter. Have a look at the difference between .then(…).catch(…) and .then(…, …) for details.
I'm trying try and catch to solve that problem
That won't work, the catch clause will still be called if there's an exception thrown by setIsLoading or handleAlert.
I get an unexpected token error. What am I doing wrong?
You have not declared the handleUpdatePanorama method as async.
To mitigate the issues and fix the syntax, you could write
async handleUpdatePanorama (panorama) {
var result
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
result = ['updateSuccess', 'success']
} catch (err) {
result = ['updateError', 'danger']
} finally {
this.setIsLoading(false)
}
this.handleAlert(...result)
},
If you need to handle errors specifically from updatePanorama, use the second argument to .then(onSuccess, onError)
handleUpdatePanorama(panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}, err => {
// handle error from updatePanorama
// you can throw err if you also want to handle the error in .catch()
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
}
note: if you return (or have no return statement) from the error handler, any subsequent .then(onSuccess will execute, if you throw an error (or return Promise.reject() for example, then the .catch() code will also run

Categories

Resources