I have an async generator function, which inside calls few async functions which can throw errors. What I want is that when error occurs, generator just logs it but then continue to work further. So i have a code like this...
async * getAll (somestuff) {
try {
const thing = await fetchThing()
const otherThing = await fetchAnother()
yield {
...thing,
...otherThing
}
} catch (error) {
console.log('Error happened, but thats ok, I want to continue')
}
}
But when error occurs, it gets logged by the catch block, but then generator yields { done: true } and operation stops.
I have tried manually yielding null after console.log in catch block but with the same result.
This kind of "problem" is not related to the generator itself, it's just related to the await mechanism inside a try..catch block, because whenever a promise is rejected inside a try-catch block, the catch is joined (unless the promises is try-catched separetely).
In fact, the generator cannot go any further, because once the catch is somehow reached, it will continue until another yield is invoked. If none needs to be invoked, it just finishes giving done: true, and that's the intended behavior of a generator.
Your main issue, is that you are expecting a generator to yield all the values, but it just can't, because the yield block is never reached:
try {
const thing = await fetchThing()
const otherThing = await fetchAnother()
yield { // <-- never met if either thing or otherThing are rejected.
...thing,
...otherThing
}
} catch (error) { // <-- this block is reached whenever either thing or otherThing raise an exception.
console.log('Error happened, but thats ok, I want to continue')
}
If you want your try..catch block to continue if either of the inner awaitable elements raised an exception, you need to try-catch them as well, so that you can have further control over their "failing" behavior:
try {
let thing, otherThing;
try {
thing = await fetchThing()
otherThing = await fetchAnother()
}
catch (innerException) {
console.log('either of the above failed!', innerException);
}
// in this way, the below block will be reached.
yield {
...thing,
...otherThing
}
} catch (error) {
console.log('Error happened, but thats ok, I want to continue')
}
In this way, whether both fails or both will be successful the yield block will be reached and will continue the execution.
Here is an example showing what said above:
const fakeAsync = async () => await Promise.resolve(true);
const fakeAsyncReject = async () => await Promise.reject(false);
async function* getAll(someStuff) {
try {
let res, raiseExc;
try {
res = await fakeAsync();
}
catch (innerResException) {
console.log('an inner exception raised.');
}
try {
raiseExc = await fakeAsyncReject();
}
catch (innerRaiseException) {
console.log('an inner exception was raised.', innerRaiseException);
}
// yield block, always reached unless there is something really wrong in this try block, like syntax errors or whatever.
yield {
res,
raiseExc
}
}
catch (error) {
// catch block raised only when the try block above yields an exception, NOT raised when either of the try-catch blocks inside the try-catch actually join the catch.
console.log('Error happened', error);
}
}
// Uncomment this block if you want to see how the for-await would work.
/*
(async() => {
for await (var res of getAll([])) {
console.log('res is', res);
}
})();
*/
(async() => {
const asyncIterator = getAll([]);
console.log(await asyncIterator.next());
})();
Related
I was reading this article. In the below code the async 'foo' function returns the result of the async 'waitAndMaybeReject' function.
I am unable to understand why the catch block in 'foo' function is not executed when 'waitAndMaybeReject' throws an error.
async function waitAndMaybeReject() {
await new Promise(r => setTimeout(r, 5000));
const isHeads = false;
//const isHeads = true;
if (isHeads) return 'yay';
throw Error('Boo!');
}
async function foo() {
try {
return waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
foo();
You need to await the waitAndMaybeReject() function in order to be able to catch the exception. As it is described in the article you refer to:
By returning waitAndMaybeReject(), we're deferring to its result, so our catch block never runs.
To make it simple, when you return waitAndMaybeReject() you don't wait for it to resolve/reject in the try ... catch block.
I'd like to catch all my exceptions in one place, but I can't do that currently:
There is an important thing to note if you like more try/catch. The following code won't catch the error:
[...]
Remember: a rejected Promise will propagate up in the stack unless you catch it. To catch the error properly in try/catch you would refactor like so:
whatever().catch(err => console.error(err));
But here is my code as I would like to have it:
async function getMtgJsonVersion() {
try {
const response = await axios(metaUrl).catch((err) => { throw err; });
const { data: { meta: { version } } } = response;
return version;
} catch (error) {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
}
}
and my test:
// console.error is mocked higher up in the test file
it.only('handles errors in fetching', async () => {
expect.assertions(2);
axios.mockReset(); // I use mockImplementationOnce elsewhere
axios.mockRejectedValueOnce(new Error('bang'));
expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
});
But when I run it, I get that the last expect is not fulfilled?
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: StringContaining "bang"
Number of calls: 0
I was hoping to catch all my throws in one place, but it looks like it's not as simple as I thought.
Is this possible, and how?
Because expect(fn).rejects.* is an asynchronous action, then it will take "a little time" to finish.
In your code, expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang')) will run before expect(() => getMtgJsonVersion()).rejects.toThrow('bang'); line. At that time, the console.log is not be called yet.
To make it work as your expectation, you have to wait until getMtgJsonVersion finishes, then assert on the log function. rejects.toThrow('bang') return a promise, then just wait for it with await keyword:
await expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
My note: Avoid using try/catch in "child" unit, just use it in the "final parent" function, if you just want to log when the http request is failed:
async function getMtgJsonVersion() {
const { data } = await axios(metaUrl).catch((error) => {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
});
return data.meta.version.
}
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
});
}
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.
If I add the keyword async to a function, it seems that I have to catch errors "in" that function. Sometimes it doesn't make sense to catch errors and I want to defer them to the caller as I may not know the context in which the function is called (e.g. is the caller doing res.json(e) or next(e), or neither)
Is there a way around this? So I can use async (in order to await inside the function) and defer errors to the caller?
Here is a really contrived example
https://codepen.io/anon/pen/OzEXwM?editors=1012
try {
function example(obj = {}) {
try {
obj.test = async () => { // if I remove async keyword, it works, otherwise I'm forced to catch errors here
//try{
throw new Error('example error') // I know `example outer error` won't catch as the returned object loses the context
//}catch(e){
// console.log('I do not want to catch error here'), I wan't to defer it to the caller
//}
}
return obj
} catch (e) {
console.log('example outer error')
}
}
let doit = example()
doit.test() // why doesn't 'outer error' catch this?
} catch (e) {
console.log('outer error')
}
The script ran as is will give an Uncaught Exception. But if I remove the async keyword it works (yes, I know in this example, async is silly, it's just an example)
Why can't I catch the error when doit.test() is called?
Usually, I would just comply and rework, but in trying to explain to someone else this morning, I realized, I didn't really know the answer.
Why can't I catch the error when doit.test() is called?
Because it is async. By the time it has reached the throw error part the try catch block on the outside has already been executed and passed. There is nothing to throw to so to speak.
To fix this, since async and await are just syntactic sugar for Promises you just use it's catch callback. Your test() function is going to return a promise so just add a the callback onto the returned promise
doit.test().catch(()=>{
console.log('Any uncaught exceptions will be sent to here now');
});
Demo
function example(obj = {}) {
obj.test = async() => {
throw new Error('example error')
}
return obj;
}
let doit = example()
doit.test().catch(e => {
console.log('Caught an exception: ', e.message);
});
If you want to catch error of test() function. You need to do await doit.test()
https://jsfiddle.net/1tgqvwof/
I wrapped your code in a anonymous function since await must be within async function
(async function () {
try {
async function example(obj = {}) {
try {
obj.test = async () => { // if I remove async keyword, it works, otherwise I'm forced to catch errors here
//try{
throw new Error('example error') // I know `example outer error` won't catch as the returned object loses the context
//}catch(e){
// console.log('I do not want to catch error here'), I wan't to defer it to the caller
//}
}
return obj
} catch (e) {
console.log('example outer error')
}
}
let doit = example()
await doit.test() // why doesn't 'outer error' catch this?
} catch (e) {
console.log('outer error')
}
})();