Try...catch vs .catch - javascript

So I have this user service functions in service.ts which includes database stuffs.
export const service = {
async getAll(): Promise<User[]> {
try {
const result = await query
return result
} catch (e) {
report.error(e)
throw new Error(e)
}
},
...
}
And query.ts file for some reasons, eg: caching, business logic, etc.
export const query = {
async index(): Promise<User[]> {
try {
const result = await service.getAll()
return result
} catch (e) {
report.error(e)
throw new Error(e)
}
},
...
}
And another upper layer for routers and resolvers, because I want to see all routes in one file.
export const resolver = {
Query: {
users: (): Promise<User[]> => query.index(),
},
...
}
Do I need to wrap try...catch in all functions? Or can't I just add .catch at the very top layer like this:
export const resolver = {
Query: {
users: (): Promise<User[]> => query.index().catch(e => e),
},
...
}

Do I need to wrap try...catch in all functions?
No, you don't, not unless you want to log it at every level for some reason. Just handle it at the top level.
In an async function, promise rejections are exceptions (as you know, since you're using try/catch with them), and exceptions propagate through the async call tree until/unless they're caught. Under the covers, async functions return promises and reject those promises when a synchronous exception occurs or when a promise the async function is awaiting rejects.
Here's a simple example:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function outer() {
// ...
await delay(10);
console.log("before calling inner");
await inner();
console.log("after calling inner (we never get here)");
}
async function inner() {
// ...
await delay(10);
console.log("inside inner");
// Something goes wrong
null.foo();
}
outer()
.catch(e => {
console.log("Caught error: " + e.message, e.stack);
});
Just as a side note: If you do catch an error because you want to do X before the error propagates and you're going to re-throw the error after you do X, best practice is to re-throw the error you caught, rather than creating a new one. So:
} catch (e) {
// ...do X...
throw e; // <== Not `throw new Error(e);`
}
But only do that if you really need to do X when an error occurs. Most of the time, just leave the try/catch off entirely.

Related

Throwing errors in async await functions and catching from where it's called

How can we catch error from an async await function from where it's called?
For example, I have a React component which calls a async-await function imported from another module. When I use Promise.reject("An unknown has occurred"); in that function, so in my React component why can't I get the error in asyncAwaitFunction.catch((e)=>console.log(e))?
I even tried throw "An unknown occured", but it doesn't seem to work.
react component
const handleSubmit = async (e) => {
e.preventDefault();
add(formData, code)
.then(() => router.push("/dashboard/manage"))
.catch((e) => setError(e)); //I want error to be catched here
};
functions.js
export const addUser = async (details, code) => {
const isExist = await isUser(code);
if (!isExist) {
const add = db.batch(); //firebase batch write
add.set(userID(code), details); //Add details to databse
add.commit()
.catch((e)=> {
console.log(e); // error occurs confirmed
Promise.reject("Unknown error occurred"); //this does't get catched in component.
});
} else {
Promise.reject("Already Exists!");
}
};
A rejected Promise (either from a Promise that you constructed that rejected, or from a Promise.reject) will only be caught if:
a .catch is added onto the end of that Promise expression, or
that Promise expression is returned inside an async function or a .then, and the caller of the async function or after the .then callback, there's a .catch
So, you should change to something like:
export const addUser = async (details, code) => {
const isExist = await isUser(code);
if (isExist) {
return Promise.reject('Already Exists!');
}
const add = db.batch(); //firebase batch write
add.set(userID(code), details); //Add details to databse
return add.commit().catch((e) => {
console.log(e); // error occurs confirmed
return Promise.reject("Unknown error occurred");
});
};
But do you really need to log in the .commit().catch? If not, it'd be cleaner to just return the commit Promise and catch in the caller:
export const addUser = async (details, code) => {
const isExist = await isUser(code);
if (isExist) {
return Promise.reject('Already Exists!');
}
const add = db.batch(); //firebase batch write
add.set(userID(code), details); //Add details to databse
return add.commit();
};
A Promise that is awaited or returned from an async function will have its errors (or its resolve value) percolate up to the caller of the async function.

How to catch error in nested Promise when async/await is used [duplicate]

I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.

Exit, reset, or clear JavaScript call stack without throw Error?

Given a number of nested promises structured similarly to the example below, is it possible, given a timeout result in delta() to neither return, resolve, nor reject the timeout, and not throw Error(timeout), but somehow exit the JavaScript call stack and reset to a new screen?
Context is React Native app
Nav library is react-native-navigation
Goal is to not throw Error(timeout)
We do not want the error to bubble up through the promise chain
The promise nesting occurs in unique variations in 100s of places in the app
We do not want individually handle a timeout response/error in each spot
Hoping instead to somehow exit the call stack and reset the screen
react-native-navigation has a slew of methods to setRoot, navigateToScreen, etc, but these don't exit call stack
const alpha = async () => {
try return await bravo()
catch (e) throw Error(e)
}
const bravo = async () => {
try return await charlie()
catch (e) throw Error(e)
}
const charlie = async () => {
try return await delta()
catch (e) throw Error(e)
}
const delta = async () => {
try {
const res = await API()
if (res === timeout) // clearCallStackAndResetToNewScreenDoNotThrowError() POSSIBLE?
else return res
} catch (e) {
throw Error(e)
}
}
const one = async () => {
try {
await alpha()
} catch (e) {
uniqueErrorHandling(e)
}
}
const two = async () => {
try {
await alpha()
} catch (e) {
uniqueErrorHandling(e)
}
}
one()
two()
etc ...

Breaking a promise chain

I'm using axios response interceptor and after I cancel a request, I need to break a promise chain. I don't want to add an error check of canceled request for all requests in my application. I've tried bluebird, but it seems it's just promise cancellation, not chain breaking.
I have to process errors in the first catch. This diagram shows the problem in general. Latest then and catch are in different files.
Promise
.then((response) => {
)
.catch((error) => {
// break promise here
})
.then((response) => {
// skip
// I don't want any extra checks here!
)
.catch((error) => {
// skip
// I don't want any extra checks here!
})
Another option is to throw a custom error that can be caught in a singular catch block at the very end like so:
const errorHandler = require('path/to/handler')
class ProcessingError1 extends Error {
constructor(message) {
super(message);
this.name = "ProcessingError1";
}
}
class ProcessingError2 extends Error {
constructor(message) {
this.message = message;
this.name = "ProcessingError2";
}
}
const process1 = async () => {
throw new ProcessingError1("Somethign went wrong");
};
const process2 = async () => {
return { some: "process" };
};
const someApiCall = async () => ({ some: "response" });
someApiCall()
.then(process1)
.then(process2) // process2 is not run if process1 throws an error
.catch(errorHandler);
// ErrorHandler.js
module.exports = e => {
if (e instanceof ProcessingError1) {
// handle error thrown from process1
}
else if (e instanceof ProcessingError2) {
// handle error thrown from process2
}
else {
// handle other errors as needed..
}
}
just keep only one catch block at the end to collect all the errors triggered by your promise chain.
if one promise throws the following in the chain are not executed
Promise
.then((response) => {
)
.then((response) => {
// skip
// I don't want any extra checks here!
)
.catch((error) => {
// skip
// I don't want any extra checks here!
})

Return or skip from catch in async function

I have a async function whose output (resolve/reject) I translate with then/catch.
I want to end the outer function with return but I can only return within catch somehow.
How can I skip/quit/return on the outside of catch or await?
await this.authService.auth(this.oldUser).then( resolve => {
//went in authService and resolve can be used
}).catch( reject => {
//in catch as authService rejected and want to return to outer
//function
return;
})
//Second attempt should only be done if first attempt "resolved"
await this.authService.auth(this.newUser).then( resolve => {
}).catch( reject => {
return;
})
You can have the .then and .catch return something meaningful that distinguishes them, and then test that distinguishing factor. For example:
const result = await this.authService.auth(this.oldUser).then((authorizedUser) => {
// do stuff with authorizedUser
return authorizedUser;
}).catch((err) => {
// handle errors, if needed
return { err };
});
if (result.err) {
// there was en error, return early:
return;
}
// rest of the code that depends on the first request being successful goes here
await this.authService.auth(this.newUser).then(...)
Note that if you're using await, it might make a bit more sense to use try/catch rather than .thens and awaits:
try {
const authorizedUser = await this.authService.auth(this.oldUser)
// do stuff with authorizedUser
// rest of the code that depends on the first request being successful goes here
const newAuthorizedUser = await this.authService.auth(this.newUser);
// do stuff with newAuthorizedUser
} catch(err) {
// handle errors, if needed
return;
}
private async authenticate(oldUser: User) {
try {
await this.authService.auth(this.oldUser).toPromise();
return;
} catch (reject) {
return;
}
}

Categories