Handle errors in a reusable fetch function - javascript

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

Related

How to validate catch block of a function in Jest?

I have a basic stringify function that looks like this ->
export const stringify = <T>(value: T) => {
try {
return JSON.stringify(value);
} catch(error){
return ''
}
}
I want to write a test that can cover the catch block of the function.
I've tried adding such a test ->
it('should be able to check for errors', async () => {
await expect(stringify('')).rejects.toThrow()
})
But this test keeps throwing errors about the function not being a promise. The function isn't going into the catch block at all.
The main function isn't a promise so I can't use the promise functions of jest.
How do I test the catch block?
There is no need to use async/await in this test. Also when there is an error you are returning '' from catch block, meaning your function will not throw anything.
Something like will work for your case
it('should be able to check for errors', () => {
expect(stringify(<error value>)).toBe('')
})
Expect the function definition to Throw
const functionDef = () => {
throw new TypeError("Error Message");
};
test("Test description", () => {
expect(functionDef).toThrow(TypeError);
expect(functionDef).toThrow("Error Message");
});

Catching errors from one function inside another JavaScript React

I have two functions, login (in fileB.js):
export const login = async (data) => {
try {
const response = await auth.login(data);
return response;
} catch (e) {
return new Error(e);
}
};
and loginProcess (in fileA.js):
const loginProcess = (data) => {
login(data)
.then((response) => {
if (response.status === 200) {
}
})
.catch((e) => {
setError(true);
});
};
If I have an error inside login() function it returns new Error(e) but inside loginProcess() the error from login() is not caught by catch but with then. I need to catch the new Error from login() inside catch in loginProcess(), how can I fix it?
You are converting promise rejection into promise fulfilment by returning an error object.
Retuning a non-promise value from the catch block will fulfil the promise returned by the login function with the return value of the catch block.
To reject the promise returned by the login function:
Re-throw the error caught by the catch block, or
Remove the try-catch block from the login function and let the calling code handle the error.
login function could be re-written as:
export const login = (data) => {
return auth.login(data);
};
I suggest that you choose the second option and re-write the login function as shown above. There is no need for a catch block that just re-throws the error.

Typescript: Variable is used before being assigned in strict mode

I'm trying to use database transaction to create a Page record however I'm getting Variable 'createdPage' is used before being assigned even though this.pagesService.create() only returns Page and it will throw error if something goes wrong so program can be sure that createdPage is set if no exception is thrown. So why I'm getting this error?
#Post('')
async create(
#Body() body: PageCreateDto,
): Promise<Page> {
let createdPage: Page;
try {
await this.database.transaction(async trx => {
createdPage = await this.pagesService.create(body, trx);
});
} catch (error) {
throw new InternalServerErrorException('unable to create page');
}
return createdPage;
}
The problem is that the function you pass into the transaction call doesn't get run synchronously and so you can't be sure that createdPage is actually assigned when you return it. You could solve this by creating a promise.
#Post('')
async create(#Body() body: PageCreateDto): Promise<Page> {
return new Promise<Page>((resolve, reject) => {
try {
await this.database.transaction(trx => this.pagesService
.create(body, trx)
.then(resolve));
} catch (error) {
reject(new InternalServerErrorException('unable to create page'));
}
});
}
Returning it inside arrow function solved the issue:
#Post('')
async create(
#Body() body: PageCreateDto,
): Promise<Page> {
let createdPage: Page;
try {
createdPage = await this.database.transaction(async trx => {
return this.pagesService.create(body, trx);
});
} catch (error) {
throw new InternalServerErrorException('unable to create page');
}
return createdPage;
}

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

Use Try/Catch Ideally

I have two function, the controller and the service. Here is the service code.
const getVersion = async (type) => {
const version = await Version.findOne({ TYPE: type }, { _id: false, VERSION: true })
return version
}
And the controller code call the getVersion function that exist in service
const getVersion = async (req, res) => {
try {
......
const version = await Version.findOne({ TYPE: type }, { _id: false, VERSION: true })
......
} catch (error) {
......
}
}
So my question is, in getVersion() function, there's an asynchronous call. Should I wrap the function in try catch, so it would look like this:
const getVersion = async (type) => {
try {
const version = await Version.findOne({ TYPE: type }, { _id: false, VERSION: true })
return version
} catch (error) {
return error
}
}
Or should i leave as it to be like the original one that use the try/catch in the root of the function? What are the advantages and the disadvantages of those two method? Thank you.
This is an anti-pattern -
const getVersion = async (type) => {
try {
const version = await Version.findOne({ TYPE: type }, { _id: false, VERSION: true })
return version
} catch (error) {
return error
}
}
The reason being that your function is labeled async it already returns a Promise. So it will either resolve version or it will reject the error.1
This is the idiomatic way to write it -
const getVersion = type =>
Version.findOne({ TYPE: type }, { _id: false, VERSION: true })
Now when you call it, a valid version response will be resolved, or some error will be rejected -
getVersion("foo").then(console.log, console.error)
1.In your getVersion you actually resolve both the successful case and the error case. This is effectively silencing the error instead of letting it bubble up to the caller. By rejecting the error, you allow the caller to handle it appropriately.
This is a similar anti-pattern -
function foo (s = "") {
if (s.length > 5)
return true
else
return false
}
Which is the less idiomatic version of -
function foo (s = "") {
return s.length > 5
}
Or as an arrow function -
const foo = (s = "") =>
s.length > 5
I suggest you leave as it is. There is no need to add try catch at different places to handle same exception. Suppose assume you are logging exception message to database. If catch is at 2 places, you will end up writing 2 logs into Db. It gives wrong perception that 2 exceptions occurred!
You should only wrap around to the code that's actually asynchronously fetching data (i.e, liable to be successful or fail). For example, this line - const version = await Version.findOne({ TYPE: type }, { _id: false, VERSION: true }). So, that if that's fails Catch block would run. You should wrap the whole function with try/catch
Should I wrap the function in try catch
You need to wrap async functions, whenever a promise is being resolved.
The await statement means there's an asynchronous call that could potentially fail and reject. If it fails and it's not within a trycatch block, then it will create an Unhandled Promise Rejection.
TLDR:
If you use the await keyword, then wrap it with a try catch.
This function requires a trycatch block
async function DoSomething() {
try {
const result = await MakeRequest(); // failure must be handled here
return result;
} catch (error) {
// Handle error
}
}
This function doesn't require a trycatch block
// This requires a trycatch block
async function DoSomething() {
return MakeRequest(); // failure can be handled by parent function
}

Categories

Resources