Handling network errors with axios and Twilio - javascript

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.

Related

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' });

Best way to use asyn/await in the following code

I am kind of new in Javascript and I want to connect to DB and run a script. Then get the result of script and run functions in order.
If there is any error with one of the functions, it should stop and doesnt run other functions.
I tried the following:
const {
Client
} = require('pg')
const client = new Client({
'connection info'
})
client.connect()
.then(() => console.log('DB connected'))
.catch(err => console.error('connection error', err.stack))
let dbResult
const data = async() => {
try {
dbResult = await client.query('SCRIPT') // array of json
} catch (error) {
console.log(error);
}
}
const func1 = async() => {
try {
// do something with dbResult
console.log('func1 success msg')
} catch (error) {
console.log('error in func1')
}
}
const func2 = async() => {
try {
// do something with dbResult
console.log('func2 success msg')
} catch (error) {
console.log('error in func2')
}
}
const func3 = async() => {
dbResult.forEach(result => {
// do something
})
try {
// do something with dbResult
console.log('func3 success msg')
} catch (error) {
console.log('error in func3')
}
}
data()
func1()
func2()
func3()
All the functions you call are async, therefore return Promises and should be awaited. You can await all of them in a try/catch block, so if one fails, the others won't execute.
Don't use try/catch in each individual function, but rather here :
const data = async() => client.query('SCRIPT') // array of json
const func1 = async() => console.log('func1 success msg')
const func2 = async() => console.log('func2 success msg')
const func3 = async() => dbResult.forEach(result => console.log(result))
(async () => {
try{
await client.connect();
let dbResult = await data();
dbResult = await func1(dbResult);
await func2();
await func3(dbResult);
} catch(err) {
console.log(err);
}
})();
await Promise.all([data, func1, func2, func3]) would also fail if one of the Promises failed, but does not guarantee the execution order.
Below is if you must use try catch inside each of your function body. If not, then I'd stick with the answer from Jeremy above.
What you can do is instead of console logging your errors that you receive in try..catch block, you can throw new error, which will stop the execution of your code and console log the actual error.
(Well, not exactly console log, but rather console.error() it)
This will prevent the execution of other functions, unless you do something with your error (make some error handling where you can execute another code, depending on the error).
In general, the syntax for this as follows:
try {
await someStuff();
} catch (err) {
throw new Error(err)
}
Object err has some additional properties, such as name and message.
Here is more about Error object.

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

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
//...
});
}
}

How to use axios client to retry on 5xx errors

I am trying add retry to my api call using axios-retry module. In order to test I am using mockoon macosx client. I have setup the endpoint in mockoon to return 502 response all the time. So that I can test the retry.
import axios from "axios";
import axiosRetry from 'axios-retry';
async function sendRequest(method): Promise<any> {
try {
// return 502 after 100ms
let url = `http://localhost:3000/answer`
axiosRetry(axios, {
retries: 3
});
const response = await axios[method](url);
console.log('api call completed');
return response;
} catch (error) {
console.log('api call error: ', error);
throw error;
}
}
(async () => {
const response = await sendRequest('get')
})()
The issue here is, the axios.get not completing the execution. Therefore it does not log either api call error or api call completed messages. Any help would be highly appreciated.
axiosRetry does not work with axios 0.19.0 (the current axios release) : https://github.com/softonic/axios-retry#note
Alternative
Use a general purpose async retry function e.g.
async function retry<T>(fn: () => Promise<T>, n: number): Promise<T> {
let lastError: any;
for (let index = 0; index < n; index++) {
try {
return await fn();
}
catch (e) {
lastError = e;
}
}
throw lastError;
}
// use
const response = await retry(() => axios[method](url), 3);
More
Source of the retry function.

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