Js: Error Handling in Async Await Case confusion - javascript

This is not any conceptual question. just want to correct my logical side.
Case 1:
try {
var to = await new IamErrorAlways()
if (to && to instanceof Error) return to // is this correct way to handle.
} catch (error) {
// report failure.
return error
}
Case 2:
try {
var to = await new IamErrorAlways()
if (!to) throw new error('Expected to to return error') // or is this correct way to handle.
} catch (error) {
// report failure.
return error // <---- catch will return awaited error
}
Out of both which one is good.

Whenever promise rejects, it will not be returned as a value it will be thrown. And only way to check for thrown error is to catch it.
This is a typical example of a promise being rejected:
const promise = function() {
return Promise.reject('hello');
};
(async () => {
try {
const promiseVal = await promise();
console.log(promiseVal);
} catch (err) {
console.log(err+' from error');
}
})();
So in this case the console.log in try block won't even execute. The catch will be executed printing hello from error.

Related

How to call an API twice if there is an error occurred?

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)

Handle errors in a reusable fetch function

I am writing a reusable fetch function (that's going to live in a separate file) and I am not too sure about the best approach to handle a function returning a null.
Say I am want to call that function and apply some logic when getOrganizationInfo doesn't return organization info. At the moment my function returns an error but falls into the first if (organization) block and I can't really handle that. I then need to use that error message to use it in the handleSubmit e.g. showError(error). What shall I do to take advantage of this else logic?
const orgName = 'ABC';
const handleSubmit = async () => {
const organization = await getOrganizationInfo(orgName);
if (organization) {
// Do something
} else {
// Do something else
}
}
Here's my function
export const getOrganizationInfo = async (
organizationName: string,
): Promise<OrganizationInfoResponse> => {
let organizationInfoResponse: OrganizationInfoResponse;
try {
const rawRes = await fetch(`/sometestendpoint/${organizationName}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
organizationInfoResponse = await rawRes.json();
if (rawRes.status >= 400) {
if (organizationInfoResponse.errorCode === ErrorCodes.INVALID_ORG_NAME) {
throw new Error('Given Organization Name is invalid');
} else {
throw new Error('Unable to get organization information.');
}
}
} catch (error) {
// organizationInfoResponse = error.toString();
throw new Error(error.toString());
}
return organizationInfoResponse;
};
The code as provided in your question will do the following when it gets into the catch block:
It executes throw new Error(error.toString())
The promise in the following expression will reject: await getOrganizationInfo(orgName)
The promise that was returned by handleSubmit will reject
None of the code in handleSubmit that follows below this await will execute
So what you claim to happen is not happening. Maybe you were talking about the version of your code where the catch block did not have that throw, but had the commented line instead:
catch (error) {
organizationInfoResponse = error.toString();
}
In that case the code will "fall into the if", because then the error is swallowed by the above catch block:
The function continues with return organizationInfoResponse
The promise in the following expression will fulfill: await getOrganizationInfo(orgName)
The function execution context of handleSubmit is restored and organization is assigned the fulfilment value (i.e. error.toString())
The if (organization) condition is truthy, and so the if block executes
Solution
To get the else block executed, use the throw version of your code, and either introduce a try...catch block in handleSubmit, or (simpler) chain a .then and .catch call on the promise:
const handleSubmit = () => {
return getOrganizationInfo(orgName).then(organisation => {
if (!organisation) throw new Error("Organisation is falsy");
// Do something
}).catch(error => {
// Do something else
});
}

There is a way to return an error inside catch without throw a new one?

I have a custom error that I call inside try. And I want to return this error inside catch without throw a new one.
const callSomething = async () => {
try {
doSomething();
} catch (error) {
// This function receive the error with the additional properties, so we need the custom error object
customErrorTreatment(error);
}
};
This function is where the error is first call.
const doSomething = async () => {
try {
// This function throw a custom error class with additional properties
throwApiError({
responseMessage: 'Some error occour',
responseStatus: 500,
});
} catch (error) {
// If I return error, the function callSomething just receive the value without error.
return error;
// I can call throwApiError again, but feels ugly, that is the only way?
return throwApiError({
responseMessage: error.responseMessage
responseStatus: error.status,
});
}
};
This is the custom error class and function
export const ApiError = class ApiError extends Error {
constructor({ responseMessage, responseStatus, error }) {
super(error);
this.responseMessage = responseMessage;
this.responseStatus = responseStatus;
}
};
const throwApiError = ({ responseMessage, responseStatus, error }) => {
throw new ApiError({ responseMessage, responseStatus});
};
Don't call throwApiError() again. Just throw error if you want the promise to stay rejected - that's how promises work.
Or get rid of your catch() handler entirely so the error just propagates naturally back up to a higher level without your intervention. You don't appear to be doing anything in the catch handler so perhaps you can just remove it.
In short No, because to generate an error you need to throw, and your method is a common method for error handling. but there is another way to manage errors like this:
const callSomething = async () => {
let { result, error } = resdoSomething();
if (error) {
return throwApiError({
responseMessage: error.responseMessage
responseStatus: error.status,
});
}
console.log(result)
// do somethings
};
and
const doSomething = async () => {
try {
let result = myfunction()
return {result: result , error : null}
} catch (error) {
return {result : null, error: error};
}
};
In this way you can reduce the number of try/catch

Javascript How do I deal with errors in Promise and makes the loop keepgoing and save the file?

I'm trying to loop through the list of files and eventually save the array in my local drive.
However, the problem is once my code confronts any error, it stops running and doesn't save anything. What I want to achieve is to keep my loop running even if there is an error.
I'm still not fully confident with using Promise,
I'm going to fetch information using below code.
function getSongs(id, number) {
return new Promise((res, rej) => {
geniusClient.getArtistSongs(id, { "page": `${number}`, "per_page": "50" }, (error, songs) => {
// console.log(error);
// console.log(JSON.parse(songs).response.songs);
if (error) {
res('error', 'id is: ', id);
} else {
let songsArray = JSON.parse(songs)
// console.log(songsArray.response.songs)
res(songsArray.response.songs);
}
})
})
}
and save the songs once I fetch all of them as below.
for (artist of resultArray) {
console.log(artist.id);
let songArray = await getSongs(artist.id, 1);
artist.songs.push(...songArray)
}
// for (artist of resultArray) {
// console.log(artist.id);
// let songArray = await getSongs(artist.id, 2);
// artist.songs.push(...songArray)
// }
roundnumber++;
console.log('round number is', roundnumber);
fs.writeFileSync('./songTest/test.json', JSON.stringify(resultArray))
Suggested approach ...
Make sure that getSongs() returns a rejected Promise for as many error cases as possible.
function getSongs(id, number) {
return new Promise((res, rej) => {
// a synchronous error here will result in Promise rejection.
geniusClient.getArtistSongs(id, { "page": `${number}`, "per_page": "50" }, (error, songs) => {
try {
if (error) {
throw error; // will be caught below and result in Promise rejection.
} else {
// an (unexpected) error thrown here will also be caught below and result in Promise rejection.
let songsArray = JSON.parse(songs);
res(songsArray.response.songs);
}
} catch(error) {
rej(error); // reject (expected/unexpected error)
}
});
});
}
In the caller code, add a try/catch structure to handle errors.
for (let artist of resultArray) {
// ^^^ don't forget to declare variables
try {
let songArray = await getSongs(artist.id, 1);
artist.songs.push(...songArray);
} catch(error) {
// catch any error arising from the try block,
// including any arising from Promise rejection in getSongs().
artist.songs.push({ 'error': error.message, 'id': artist.id }); // or whatever you want to represent an error
}
}
You could use Promise.allSettled. From the MDN docs
The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.
Store the promises in an array, don't await them and pass that array to Promise.allSettled.
With this all your errors (if any) will be stored and returned to you in array at the end of the operation.

How to test status of a promise inside Promise.finally() without awaiting it in production code

I am using Promise.prototype.finally() (or try-catch-finally in an async function) in my production code to execute some follow-up code without changing resolution/rejection status of the current promise.
However, in my Jest tests, I would like to detect that the Promise inside finally block wasn't rejected.
edit: But I don't want to actually await the Promise in my "production" code (there I care only about errors re-thrown from catch, but not about errors from finally).
How can I test for that? Or at least how to mock the Promise.prototype to reject the current promise on exceptions from finally?
E.g. if I would be testing redux action creators, the tests pass even though there is a message about an unhandled Promise rejection:
https://codesandbox.io/s/reverent-dijkstra-nbcno?file=/src/index.test.js
test("finally", async () => {
const actions = await dispatchMock(add("forgottenParent", { a: 1 }));
const newState = actions.reduce(reducer, undefined);
expect(newState).toEqual({});
});
const dispatchMock = async thunk => {...};
// ----- simplified "production" code -----
const reducer = (state = {}, action) => state;
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
dispatch(get(parentId)); // tests pass if the promise here is rejected
}
};
const get = id => async dispatch => {
dispatch("get start");
try {
await someFetch(id);
dispatch("get success");
} catch (e) {
dispatch("get failed");
throw e;
}
};
const someFetch = async id => {
if (id === "forgottenParent") {
throw new Error("imagine I forgot to mock this request");
}
Promise.resolve(id);
};
dispatch(get(parentId)); // tests pass if an exception is thrown here
There is no exception throw in that line. get(parentId) might return a rejected promise (or a pending promise that will get rejected later), but that's not an exception and won't affect control flow.
You might be looking for
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
await dispatch(get(parentId));
// ^^^^^
}
};
Notice that throwing exceptions from a finally block is not exactly a best practice though.
edit: more general solutions available on https://stackoverflow.com/a/58634792/1176601
It is possible to store the Promise in a variable accessible in some helper function that is used only for the tests, e.g.:
export const _getPromiseFromFinallyInTests = () => _promiseFromFinally
let _promiseFromFinally
const add = parentId => async dispatch => {
...
} finally {
// not awaited here because I don't want to change the current Promise
_promiseFromFinally = dispatch(get(parentId));
}
};
and update the test to await the test-only Promise:
test("finally", async () => {
...
// but I want to fail the test if the Promise from finally is rejected
await _getPromiseFromFinallyInTests()
});

Categories

Resources