I've got some basic Javascript code that calls a stock API with symbols where the symbols are provided from a simple HTTP call like this:
GET http://localhost:4000/batch_stock_prices/?stocks=12312.
I believe I am misunderstanding the syntax for how to catch an exception from a promise..
An exception gets thrown that 12312 is an invalid symbol which I expect, on the terminal running the node server I see the exception but I'm not able to pass it back in the HTTP response. The error that's passed back in the response is 'undefined'. How can I catch the exception? Do I need a try catch somewhere?
const fetch = require('node-fetch')
const { IEXCloudClient } = require("node-iex-cloud");
const { type } = require('tap');
const iex = new IEXCloudClient(fetch, {
sandbox: true,
publishable: "pk_2f78524e5........23c327e24b5",
version: "stable"
});
'use strict'
async function getCurrentPriceOfBatchStocks(_stock) {
stocks_to_submit = _stock['stocks'];
console.log(stocks_to_submit)
response = await iex
.batchSymbols(stocks_to_submit)
.price()
.catch(function (error) { // <-- doesn't seem to get called
console.log("Exception: " + error);
throw error;
})
return new Promise((resolve, reject) => {
if (response) {
resolve(response)
} else {
reject(response); // <-- response is undefined. why?
}
});
}
const batchStocksSchema = {
querystring: {
type: 'object',
properties: {
stocks: {
type: 'string'
}
},
required: ['stocks']
}
}
module.exports = async function (fastify, opts) {
fastify.get('/batch_stock_prices/', {
schema: batchStocksSchema
}, async function (request, reply) {
try {
var response = await getCurrentPriceOfBatchStocks(request.query)
// console.log(response)
return reply
.code(200)
.send(response);
} catch (e) {
console.log(e)
return reply
.code(400)
.send('Bad Request, exception: ' + e) // outputs: ...exception: undefined
}
})
}
It's hard to say for sure what's wrong without running the code, but there are several issues with the use of async, await, and promises in the code, and with creating implicit globals. (Also various missing ;.) If we sort those out, it may be that whatever error is occurring will stop being obscured. See *** comments:
"use strict"; // *** This has to be at the very beginning of the compilation
// unit, it can't be later in the code as it is in the question
const fetch = require('node-fetch')
const { IEXCloudClient } = require("node-iex-cloud");
const { type } = require('tap');
const iex = new IEXCloudClient(fetch, {
sandbox: true,
publishable: "pk_2f78524e5........23c327e24b5",
version: "stable"
});
async function getCurrentPriceOfBatchStocks(_stock) {
// *** Declare `stocks_to_submit`
const stocks_to_submit = _stock['stocks'];
// *** Declare `response`
const response = await iex.batchSymbols(stocks_to_submit).price();
// *** Don't catch the error, let it propagate; the caller should
// know whether the call succeeded or failed
// *** Don't use `new Promise`, there's no purpose to it
return response;
}
const batchStocksSchema = {
querystring: {
type: 'object',
properties: {
stocks: {
type: 'string'
}
},
required: ['stocks']
}
};
// *** This function never uses `await`, so don't make it `async`
module.exports = function (fastify, opts) {
fastify.get('/batch_stock_prices/', {
schema: batchStocksSchema
}, function (request, reply) { // *** Typically old-style callback APIs don't do
// anything with the promise an `async` function
// returns, so don't pass `async` functions into them
getCurrentPriceOfBatchStocks(request.query)
.then(response => {
// *** No `return` here, we aren't resolving the promise from `then` with the result
// of `send`
reply
.code(200)
.send(response);
})
.catch(e => {
console.log(e);
// *** No `return` here, we aren't resolving the promise from `catch` with the
// result of `send`
reply
.code(400)
.send('Bad Request, exception: ' + e);
});
});
};
For why the catch is not called in this part:
response = await iex
.batchSymbols(stocks_to_submit)
.price()
.catch(function (error) { // <-- doesn't seem to get called
console.log("Exception: " + error);
throw error;
})
and why response is undefined:
return new Promise((resolve, reject) => {
if (response) {
resolve(response)
} else {
reject(response); // <-- response is undefined. why?
} });
This is the cause:
The promise returned by price() call had resolves an undefined value (instead of rejecting with an error). Your "await" wait for this undefined value and assigned it to "response" variable.
The price() when having problem have already handled the error and then print the details in the console:
Error: <html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx</center>
</body>
</html>
at IEXRequest.<anonymous> (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:128:35)
at step (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:32:23)
at Object.next (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:13:53)
at fulfilled (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:4:58)
It wasn't really passing the error back in the chain to your code.
So on your question of "How can I catch the exception?". Unfortunately you probably has no way to receive the exception details (unless you can control the error handling behaviour of iex). You may consider to check whether the result is undefined and handle accordingly.
Related
I encountered a weird issue where I couldn't define a variable inside an anonymous function which had a try catch block defined in it.
let response: AxiosResponse<CustomType[]>; // had to define outside the useQuery
const { data: info } = useQuery(
['queryKey', a, b],
async () => {
// let response: AxiosResponse<CustomType[]>; //ERROR variable response is used before being assigned
try {
response = await getAxios().get(`requestURL`);
const responseFiltered = {};
response.data.forEach((a) => {
responseFiltered[a] = a;
})
return responseFiltered;
} catch (error) {
logger.error({
meta: { error, response}, // variable used here
});
}
}
);
Not sure why it expects the response variable to be defined outside the useQuery function.
You are telling the compiler that response will be of type AxiosReponse. However, you are not giving response a "value" in branches. response is assigned inside the try/catch, so in the catch clause, it might still be undefined. That's what TypeScript is trying to tell you.
To fix this, define your response as being potentially undefined:
let response: AxiosResponse<CustomType[]> | undefined
Then you can use it in the catch block. Even though, very likely, in the catch block it will be undefined if the network request fails.
With axios, the error is actually an AxiosError, which will contain the response, so maybe you'd want something like this?
const { data: info } = useQuery(
['queryKey', a, b],
async () => {
try {
const response = await axios.get(`requestURL`);
return response;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error({
meta: { error, response: error.response },
});
}
throw error
}
}
);
Please further keep in mind that catching an error just to log it is not ideal with react-query, because it "swallows" the error. You can see that I've re-thrown the error at the end of the catch block. A better approach would be to use the onError callback:
const { data: info } = useQuery(
['queryKey', a, b],
async () => {
return await axios.get(`requestURL`);
}, {
onError: (error) => {
if (axios.isAxiosError(error)) {
console.error({
meta: { error, response: error.response },
});
}
}
})
I have an internal API that I would like to post data. Depends on some cases, I am seeing errors. So what I would like to do is to call it again if there is an error occurred.
What I did was to create a counter to pass it to the function and call the function recursively as below. This gives me the error as below:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Here is how I call the function:
....
private RETRY_API = 1;
....
try {
await this.callAPI(request, this.RETRY_API);
} catch (error) {
console.log('error', error);
}
This program never comes to the catch block above.
And here is my actual function that I call the API:
private async callAPI(request, retry) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request, async(err: any, httpCode: number, data) => {
if (this.RETRY_API == 2) {
return reject(err);
} else if (err) {
this.callAPI(request, retry);
this.RETRY_API++;
} else if ( httpCode !== 200 ) {
this.RETRY_API = 2;
// some stuff
} else {
this.RETRY_API = 2;
// some stuff
return resolve(data);
}
});
})
}
Not sure what I am missing. If there is a better way to call the API twice if an error occurred, that would be great if you let me know.
Let's organize a little differently. First, a promise-wrapper for the api...
private async callAPI(request) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request,(err: any, httpCode: number, data) => {
err ? reject(err) : resolve(data);
});
});
}
A utility function to use setTimeout with a promise...
async function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Now, a function that calls and retries with delay...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
return await callAPI(request);
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
If you can't force a failure on the api to test the error path some other way, you can at least try this...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
// I hate to do this, but the only way I can test the error path is to change the code here to throw an error
// return await callAPI(request);
await delay(500);
throw("mock error");
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
It looks like you need to add return await to the beginning of the line this.callAPI(request, retry); in callAPI function.
Similarly there are some condition blocks that doesn't resolve or reject the promise. While it might work okay, it's considered bad practice. You want to either resolve or reject a promise.
I've accomplished calling an API a second time when I received an error by using axios' interceptors functions.
Here is a code snippet you can review:
axios.interceptors.response.use(
// function called on a successful response 2xx
function (response) {
return response;
},
// function called on an error response ( not 2xx )
async function (error) {
const request = error.config as AxiosRequestConfig;
// request is original API call
// change something about the call and try again
// request.headers['Authorization'] = `Bearer DIFFERENT_TOKEN`;
// return axios(request)
// or Call a different API
// const new_data = await axios.get(...).then(...)
// return new_data
// all else fails return the original error
return Promise.reject(error)
}
);
Try replacing
if (this.RETRY_API == 2)
with
if (this.RETRY_API > 1)
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
}
I have a package that simply does an HTTP call and returns either a success response or an error response. I am trying to make it so that you get IntelliSense on both success and error.
This is what I have:
class ResultSuccess {
userId: number;
id: number;
title: string;
}
class ResultError {
error: boolean;
}
export function magic(): Promise<ResultSuccess> {
return new Promise((resolve, reject) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => resolve(plainToClass(ResultSuccess, json as ResultSuccess)))
.catch(err => {
reject(plainToClass(ResultError, { error: true } as ResultError));
});
});
}
This works, and I get intelisense on the outcome but if i motify the retun to somethign like:
function magic(): Promise<ResultSuccess | ResultError>
I no longer get intelisense on the success or fail outcomes.
I'm new to typescript, can someone suggest a way to hanlde this or can someone see an issue?
Solution #1: errors are thrown
I'm new to typescript
In this case I allow myself to rewrite your magic function using async and await, because it is the way to work in 2019:
export async function magic(): Promise<ResultSuccess> {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const json = await response.json();
return plainToClass(ResultSuccess, json as ResultSuccess);
} catch (err) {
throw plainToClass(ResultError, { error: true });
}
}
The return value is a promise of ResultSuccess. The function never returns a ResultError, but it can throw it. An example on how to use it:
async function useMagic() {
try {
const result = await magic();
// 'result' is of type 'ResultSuccess'
} catch (err) {
// 'err' is of type 'any' and you know it is a 'ResultError'
}
}
Solution #2: errors are not thrown but returned
If you decide that errors must be returned as result values, you can do that:
export async function magic2(): Promise<ResultSuccess | ResultError> {
try {
// … same code as previously …
} catch (err) {
return plainToClass(ResultError, { error: true });
}
}
Then, when you use the result value, you have to determine if this is an error or a success. Here is a solution:
Write a type guard:
function isResultError(result: ResultSuccess | ResultError): result is ResultError {
return result["error"] !== undefined;
}
Then, use it:
async function useMagic2() {
const result = await magic2();
if (isResultError(result)) {
// Here, 'result' is of type 'ResultError'
} else {
// Here, 'result' is of type 'ResultSuccess'
}
}
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