I'm trying to test the error handling portion of my function but I am not sure how to do this... I am using someone's API that is always running, so how can I simulate the API not connecting?
async function getElephant() {
const proxyurl = 'https://cors-anywhere.herokuapp.com/'
const url = 'https://elephant-api.herokuapp.com/elephants/random'
fetch(proxyurl + url)
.then((resp) => { return resp.json() })
.then((data) => {
data.forEach((elephant) => {
const { name, sex, species, note } = elephant
document.getElementById('name').value = name
document.getElementById('gender').value = sex
document.getElementById('species').value = species
document.getElementById('about').value = note
})
})
// .catch(() => console.log("Can't access " + url + " blocked?"))
.catch(() => ("Can't access"))
}
My test:
test('Test .catch block, failure message to connect to url', async () => {
expect.assertions(1);
return expect(getElephant()).rejects.toEqual('Can't access');
})
and also tried using fetch-mock utility
test('Test .catch block, failure message to connect to url', async () => {
const url = 'https://lephant-api.herokuapp.com/elephants/random'; //Try misspelling url to catch error
fetchMock.get(url, {
status: 400,
body: JSON.stringify('BAD CONNECTION')
})
const response = await getElephant(url)
const result = await response.json()
expect(result).toThrow("Can't access")
})
Any advice is appreciated!
I hope this example helps you how to handle errors with try catch
function addTask() {
x = "test input";
try {
if(x == "") throw "empty"; // error cases
if(isNaN(x)) throw "not a number";
x = Number(x);
if(x > 10) throw "too high";
}
catch(err) { // if there's an error
console.error("Input is " + err); // write the error in console
}
finally { // Lets you execute code, after try and catch, regardless of the result
console.log("Done");
}
}
addTask();
Related
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
)
);
});
});
I am trying to throw an error to the calling function and the error is not getting captured. The exception is not propagating to the calling function.
'use strict';
const { Pool } = require('pg');
const pool = new Pool();
var result;
exports.handler = async (event) => {
var payload = event;
try{
result = await insertOrder(payload, 'test');
}
catch (err) {
console.error("Error from main: " + err);
throw err ;
}
return result;
};
async function insertOrder(payload, name)
{
const client = await pool.connect();
try{
const queryString = {
text: "INSERT INTO public.orders(payload, shop_name)" +
"VALUES ($1, $2) RETURNING id",
values: [payload, name],
};
const result = await client.query(queryString);
var orderId = result.rows[0].id;
}
catch (err) {
await client.query('ROLLBACK');
console.log("Error from child: " + err);
throw err;
}
finally {
client.release();
return orderId;
}
}
Here is what is written to the log:
INFO Error from child: error: INSERT has more target columns than expressions
The console.error in the calling function is not written to the log. What am I am missing? TIA!
Moving return orderId; to try block solved my issue
I'm writing a test for some code that will use Promise.race to bring back a result from a graphql service that is on (could be on) multiple servers. I've used Nock to mock the request, which works fine when I'm hitting a single service. When I mock up multiple services, Nock throws an error saying
AssertionError: expected [Function] to not throw an error but 'Error: Error: Nock: No match for request {\n "method": "POST",\n "url": "http://94.82.155.133:35204",\n "headers": {\n "content-type": "application/json",\n "accept": "application/json"\n },\n "body": "{...}"\n}' was thrown
my test looks like this:
it('should make two POST requests to the service for data from graphQL', async () => {
const spy = sinon.spy(releases, '_queryGraphQL');
const releaseID = 403615894;
nock.cleanAll();
const services = serviceDetails(NUMBER_OF_SERVICES); // NUMBER_OF_SERVICES = 3
nock(serviceDiscoveryHost)
.get('/v1/catalog/service/state51')
.reply(HTTP_CODES.OK, services);
for (const service of services) {
const currentNodeHealth = nodeHealth(service.Node);
nock(serviceDiscoveryHost)
.get('/v1/health/node/'+service.Node)
.reply(HTTP_CODES.OK, currentNodeHealth);
const delayTime = Math.floor(Math.random()*1000);
nock('http://'+service.Address+':'+service.ServicePort, serviceHeaders)
.post('/')
.delay(delayTime)
.replyWithError({code: 'ETIMEDOUT', connect: false})
.post('/')
.delay(delayTime)
.reply(HTTP_CODES.OK, getReply(releaseID))
}
const actual = await releases.getRelease(releaseID)
.catch((err) => {
console.log(releases._retries);
(() => { throw err; }).should.not.throw();
});
expect(releases._retries[releaseID]).to.be.equal(1);
expect(spy.callCount).to.be.equal(2);
expect(actual).to.be.an('object')
expect(actual.data.ReleaseFormatById.id).to.be.equal(releaseID);
});
and the offending bit of code looks like
async _queryGraphQL(releaseID, services) {
if (! this._retries[releaseID]) {
this._retries[releaseID] = 0;
}
const postData = this._getReleaseQuery(releaseID);
return Promise.race(services.map( (service) => {
const options = this._getHTTPRequestOptions(service);
return new Promise((resolve, reject) => {
let post = this.http.request(options, (res) => {
let data = '';
if (res.statusCode < 200 || res.statusCode > 299) {
const msg = this.SERVICE_NAME + ' returned a status code outside of acceptable range: ' + res.statusCode;
reject(new QueryError(msg, postData));
} else {
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('error', (err) => {
reject(new QueryError(err.message, postData, err));
});
res.on('end', () => {
resolve(JSON.parse(data));
});
}
});
post.on('error', async (err) => {
if (err.code === 'ETIMEDOUT') {
if (this._retries[releaseID] &&
this._retries[releaseID] === 3) {
reject(err);
} else {
this._retries[releaseID] += 1;
resolve(this._queryGraphQL(releaseID, services));
}
} else {
reject(new QueryError(err.message, postData, err));
}
});
post.write(JSON.stringify(postData));
post.end();
});
}));
}
this.http is just require('http');. and the options will be {hostname: service.hostname} \\ example.com etc.
What I'm expecting, is that if the first service to respond, responds with an error relating to: 'ETIMEDOUT', it'll recall the function (upto 2 more times) and try all the services again until the first service to respond is something that isn't a 'ETIMEDOUT'.
I have an action that call some api if status return 404, I want to return it from catch. And I call this action directly in react component but not recive it from action.
action
const someAction = (tagId, token) => dispatch => {
dispatch({
type: TagAssignmentActionTypes.TagAssignmentChanged,
})
let status, assignee, response
try {
response = await DeviceApi.checkTagAssignment(tagId, token)
assignee = response.result.assignee
status = response.result.status
return response.result
} catch (e) {
console.log(e, 'eeeeeeee')
if (e && e.status === httpStatusCode.notFound)
status = TagStatus.NotFound
return status
}
dispatch({
type: TagAssignmentActionTypes.TagAssignmentChanged,
status,
assignee,
response,
})
}
function in react component
lookupComplete = (tagId = this.state.tagId) => this.setState({tagId}, async () => {
let person, status
let result = await this.props.someAction(parseInt(tagId, 16), this.props.accessToken)
console.log(result.status, 'status')
status = result.status
person = this.props.persons[result.assignee]
person
? this.triggerTransition(transitions.ClickCheckTag, {person, status}) : this.triggerTransition(transitions.Free)
})
How to return status from catch in case of error ?
In your try statement you need to check if status equals 404 and then throw an error. Then your catch statement will execute.
try {
response = await DeviceApi.checkTagAssignment(tagId, token);
assignee = response.result.assignee
status = response.result.status;
if (status === httpStatusCode.notFound){
var error404 = {
message: "not found",
status: httpStatusCode.notFound
};
throw error404 ;
} else {
return response.result;
}
}
Throw statement documentation
I have a nodejs app which uses express. When I try to call this method below I get logged in console "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client".
I found some other threads here about it being the async-methods, but I cant seem to pinpoint where its wrong in my code. I tried console logging, and the last place that logs before the return is just after this try catch. I also added "Return" before sending res.status to all my code, but it still emits this error. The code in the controller is much longer, but no console.logs where made after this piece of code. Any idas what Im doing wrong?
router.post('/add', auth, async (req, res) => {
if(req.body.mobilenumber === ''){
return res.status(400).send('Bad input data, please check input.')
}
const checkUserText = 'SELECT * FROM SQL';
let dbResult1;
try {
dbResult1 = await GetStuff(checkUserText, [req.body.mobilenumber]);
} catch (err) {
logger.error(err)
return res.status(500).send(err)
}
if (dbResult1.rowCount === null || dbResult1.rowCount >= 1) {
return res.sendStatus(405).send('User already exist')
}
var insertUser = 'INSERT INTO SQL RETURNING *';
let dbResult2;
try {
let insData = [paramsFromBody];
dbResult2= await GetStuff(insertUser, insData);
} catch (err) {
logger.error(err)
return res.status(500).send(err)
}
if (dbResult2 === null || dbResult2.rowCount === 0) {
return res.status(500).send('error')
logger.error('Error')
}
return res.status(200).send('Added OK.')
})
async function GetStuff(text, id){
try {
return await db.query(text, id);
} catch(error) {
throw new Error('Failed DB-action' + error);
}
}
From what I see in the code you've shared, this will cause the error you report:
return res.sendStatus(405).send('User already exist')
because .sendStatus() sends the whole response immediately and then .send() tries to send another response. That should be this:
return res.status(405).send('User already exist');
Also, you can simplify GetStuff() to just this:
function GetStuff(text, id) {
return db.query(text, id).catch(err => {
throw new Error('Failed DB-action' + err);
});
}
One clue is that there's pretty much never a reason to do:
return await fn();
You can just do:
return fn();
Both return the same promise with the same resolved value.
Also, note that in this code:
if (dbResult2 === null || dbResult2.rowCount === 0) {
return res.status(500).send('error')
logger.error('Error')
}
The logger.error() will never get called since it's after a return statement.
Your code could be simplified by using one central try/catch and only sending the response in two places (one for success and one for error):
// our own error subclass that holds a status value
class ResponseError extends Error {
constructor(msg, status) {
super(msg);
this.status = status;
}
}
router.post('/add', auth, async (req, res) => {
try {
if (req.body.mobilenumber === '') {
throw new ResponseError('Bad input data, please check input.', 400);
}
const checkUserText = 'SELECT * FROM SQL';
let dbResult1 = await GetStuff(checkUserText, [req.body.mobilenumber]);
if (dbResult1.rowCount === null || dbResult1.rowCount >= 1) {
throw new ResponseError('User already exist', 405);
}
const insertUser = 'INSERT INTO SQL RETURNING *';
let dbResult2 = await GetStuff(insertUser, [paramsFromBody]);
if (dbResult2 === null || dbResult2.rowCount === 0) {
throw new ResponseError("dbResult2 empty", 500);
}
res.send('Added OK.')
} catch(e) {
// if no specific status specified, use 500
let status = e.status || 500;
res.status(status).send(e);
}
})
function GetStuff(text, id) {
return db.query(text, id).catch(err => {
throw new Error('Failed DB-action' + error);
});
}