Recently I embraced promises chaining pattern. It's very convenient to do like this:
action1
.then(()=> action2())
.then(()=> action3());
But, in order to do it I changed all my methods like this (TypeScript):
action1() : Promise<any>{
try{
// actual code
return Promise.resolve();
} catch (err){
console.error(err);
return Promise.reject(err);
}
}
This looks like very repetitive pattern. What's the best way to avoid code duplication?
Write a function to wrap promise over a function and you can reuse it
wrapPromise(fun) : Promise<any>{
try{
var value = fun()
return Promise.resolve(value);
} catch (err){
console.error(err);
return Promise.reject(err);
}
}
wrapPromise(action1).then()
Since you are using typescript, you'll want to use async/await. Simply do
async action1(): Promise<any>{
try {
return // actual code;
} catch (err){
console.error(err);
throw err;
}
}
However there's a good chance you actually don't want to catch, log and rethrow all errors in every function, so this simplifies to
async action1(): Promise<any>{
return // actual code;
}
Related
In order to minimize code duplication, I've written some simple JS/TS async functions by encapsulating the respective synchronous function within a Promise. But, not seeing this elsewhere, I'm concerned that I may be missing some design problem(s).
For example:
function fileExistsSync(path: string) {
try {
const result = Deno.statSync(path);
return result.isFile;
} catch (err) {
if (err instanceof Deno.errors.PermissionDenied) {
throw err;
}
return false;
}
}
async function fileExists(path: string) {
return await Promise.resolve().then(() => fileExistsSync(path));
}
async function fileExistsAsync(path: string) {
try {
const result = await Deno.stat(path);
return result.isFile;
} catch (err) {
if (err instanceof Deno.errors.PermissionDenied) {
throw err;
}
return false;
}
}
Is there any observable difference between the functions fileExists() and fileExistsAsync()?
Yes, because fileExists uses Deno.statSync where fileExistsAsync uses Deno.stat.
The sync versions of IO functions block the entire JS execution runtime, while the promise/async Deno.stat version allows other JS code to continue to execute while waiting for a result.
function sendPushNotification(subscription, urlEncodedData){
try {
webpush.sendNotification(subscription, urlEncodedData);
} catch (err){
console.log('err');
console.log(err);
}
}
This doesn't catch the error, it is still considered an 'unhandledRejection'. Why?
If you're calling an async function, or a function that returns a Promise then you need to sequence things properly:
async function sendPushNotification(subscription, urlEncodedData){
try {
await webpush.sendNotification(subscription, urlEncodedData);
} catch (err){
console.log('err');
console.log(err);
}
}
Here await will capture any response. An error condition that will manifest as an exception.
Note that this makes sendPushNotification() return a Promise so you will have to treat it as asynchronous. This means the caller needs to await as well, and that may have impacts up the call chain depending on your sequencing requirements.
I'm making a function to return a boolean to know if an element is present or not, but I get
this is the status: false
How can I get an only true or false response, please help, I'm using an if condition but it shows that response...
async IsPresent(element){
try{
await element.isPresent().then(function(status){
console.log("this is the status: " + status);
return status;
});
}
catch(err){
return console.log(err);
}
};
this is the function where I'm calling IsPresent
async ClickSomething(){
try{
await this.helpers.ClickElement(oneElement);
if(await this.helpers.IsPresent(anotherElement) == false){
//Do something
}else{
//Do something esle
}
catch(err){
return console.log(err);
}
};
There is a lot of unnecessary code in that function. You could achieve the same functionality using the following
async IsPresent(element){
try {
return await element.isPresent();
}
catch (err) {
return console.log(err);
}
};
First thing to note is that when a function is declared async you are able to use the await keyword to pause the execution until that any associated promise is resolved. You are actually using two different approached to resolve promises here (promise.resolve and .then()) when only awaits are necessary.
Second thing to note is that isPresent return true or false anyway so that can be directly returned from your function.
I like the flatness of the new Async/Await feature available in Typescript, etc. However, I'm not sure I like the fact that I have to declare the variable I'm awaiting on the outside of a try...catch block in order to use it later. Like so:
let createdUser
try {
createdUser = await this.User.create(userInfo)
} catch (error) {
console.error(error)
}
console.log(createdUser)
// business
// logic
// goes
// here
Please correct me if I'm wrong, but it seems to be best practice not to place multiple lines of business logic in the try body, so I'm left only with the alternative of declaring createdUser outside the block, assigning it in the block, and then using it after.
What is best practice in this instance?
It seems to be best practice not to place multiple lines of business logic in the try body
Actually I'd say it is. You usually want to catch all exceptions from working with the value:
try {
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
// business logic goes here
} catch (error) {
console.error(error) // from creation or business logic
}
If you want to catch and handle errors only from the promise, you have three choices:
Declare the variable outside, and branch depending on whether there was an exception or not. That can take various forms, like
assign a default value to the variable in the catch block
return early or re-throw an exception from the catch block
set a flag whether the catch block caught an exception, and test for it in an if condition
test for the value of the variable to have been assigned
let createdUser; // or use `var` inside the block
try {
createdUser = await this.User.create(userInfo);
} catch (error) {
console.error(error) // from creation
}
if (createdUser) { // user was successfully created
console.log(createdUser)
// business logic goes here
}
Test the caught exception for its type, and handle or rethrow it based on that.
try {
const createdUser = await this.User.create(userInfo);
// user was successfully created
console.log(createdUser)
// business logic goes here
} catch (error) {
if (error instanceof CreationError) {
console.error(error) // from creation
} else {
throw error;
}
}
Unfortunately, standard JavaScript (still) doesn't have syntax support for conditional exceptions.
If your method doesn't return promises that are rejected with specific enough errors, you can do that yourself by re-throwing something more appropriate in a .catch() handler:
try {
const createdUser = await this.User.create(userInfo).catch(err => {
throw new CreationError(err.message, {code: "USER_CREATE"});
});
…
} …
See also Handling multiple catches in promise chain for the pre-async/await version of this.
Use then with two callbacks instead of try/catch. This really is the least ugly way and my personal recommendation also for its simplicity and correctness, not relying on tagged errors or looks of the result value to distinguish between fulfillment and rejection of the promise:
await this.User.create(userInfo).then(createdUser => {
// user was successfully created
console.log(createdUser)
// business logic goes here
}, error => {
console.error(error) // from creation
});
Of course it comes with the drawback of introducing callback functions, meaning you cannot as easily break/continue loops or do early returns from the outer function.
Another simpler approach is to append .catch to the promise function. ex:
const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
Cleaner code
using async/await with Promise catch handler.
From what I see, this has been a long-standing problem that has bugged (both meanings) many programmers and their code. The Promise .catch is really no different from try/catch.
Working harmoniously with await/async, ES6 Promise's catch handler provides a proper solution and make code cleaner:
const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))
console.log(createdUser)
// business
// logic
// goes
// here
Note that while this answers the question, it gobbles up the error. The intention must be for the execution to continue and not throw. In this case, it's usually always better to be explicit and return false from catch and check for user:
.catch(error => {
console.error(error);
return false
})
if (!createdUser) // stop operation
In this case, it is better to throw because (1) this operation (creating a user) is not expected to failed, and (2) you are likely not able to continue:
const createUser = await this.User
.create(userInfo)
.catch(error => {
// do what you need with the error
console.error(error)
// maybe send to Datadog or Sentry
// don't gobble up the error
throw error
})
console.log(createdUser)
// business
// logic
// goes
// here
Learning catch doesn't seem like worth it?
The cleanliness benefits may not be apparent above, but it adds up in real-world complex async operations.
As an illustration, besides creating user (this.User.create), we can push notification (this.pushNotification) and send email (this.sendEmail).
this.User.create
this.User.create = async(userInfo) => {
// collect some fb data and do some background check in parallel
const facebookDetails = await retrieveFacebookAsync(userInfo.email)
.catch(error => {
// we can do some special error handling
// and throw back the error
})
const backgroundCheck = await backgroundCheckAsync(userInfo.passportID)
if (backgroundCheck.pass !== true) throw Error('Background check failed')
// now we can insert everything
const createdUser = await Database.insert({ ...userInfo, ...facebookDetails })
return createdUser
}
this.pushNotifcation and this.sendEmail
this.pushNotification = async(userInfo) => {
const pushed = await PushNotificationProvider.send(userInfo)
return pushed
})
this.sendEmail = async(userInfo) => {
const sent = await mail({ to: userInfo.email, message: 'Welcome' })
return sent
})
Compose the operations:
const createdUser = await this.User
.create(userInfo)
.catch(error => {
// handle error
})
// business logic here
return await Promise.all([
this.pushNotification(userInfo),
this.sendEmail(userInfo)
]).catch(error => {
// handle errors caused
// by pushNotification or sendEmail
})
No try/catch. And it's clear what errors you are handling.
I usually use the Promise's catch() function to return an object with an error property on failure.
For example, in your case i'd do:
const createdUser = await this.User.create(userInfo)
.catch(error => { error }); // <--- the added catch
if (Object(createdUser).error) {
console.error(error)
}
If you don't like to keep adding the catch() calls, you can add a helper function to the Function's prototype:
Function.prototype.withCatcher = function withCatcher() {
const result = this.apply(this, arguments);
if (!Object(result).catch) {
throw `${this.name}() must return a Promise when using withCatcher()`;
}
return result.catch(error => ({ error }));
};
And now you'll be able to do:
const createdUser = await this.User.create.withCatcher(userInfo);
if (Object(createdUser).error) {
console.error(createdUser.error);
}
EDIT 03/2020
You can also add a default "catch to an error object" function to the Promise object like so:
Promise.prototype.catchToObj = function catchToObj() {
return this.catch(error => ({ error }));
};
And then use it as follows:
const createdUser = await this.User.create(userInfo).catchToObj();
if (createdUser && createdUser.error) {
console.error(createdUser.error);
}
#Bergi Answer is good, but I think it's not the best way because you have to go back to the old then() method, so i think a better way is to catch the error in the async function
async function someAsyncFunction(){
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
}
someAsyncFunction().catch(console.log);
But what if we have many await in the same function and need to catch every error?
You may declare the to() function
function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
And then
async function someAsyncFunction(){
let err, createdUser, anotherUser;
[err, createdUser] = await to(this.User.create(userInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`createdUser is ${createdUser}`);
[err, anotherUser] = await to(this.User.create(anotherUserInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`anotherUser is ${anotherUser}`);
}
someAsyncFunction();
When reading this its: "Wait to this.User.create".
Finally you can create the module "to.js" or simply use the await-to-js module.
You can get more information about to function in this post
await this.User.create(userInfo).then(async data => await this.emailService.sendEmail(data.email), async error => await this.sentryService.sendReport(error))
This question already has answers here:
Nodejs Mongoose - how to avoid callback hell?
(3 answers)
Closed 5 years ago.
I am working with Mongoose ODM for MongoDB in a node express-based web application which provides its API in a callback fashion. This fashion creates a callback hell to populate documents.
Edit 1: I have added the current working code.
User.remove({}, (err) => {
if (err) return console.error(err)
let admin = new User({
email: 'admin#dap.com',
password: 'dapdap'
})
admin.save((err, admin) => {
if (err) return console.error(err)
console.info('Success')
mongoose.disconnect()
})
})
Is there an elegant way to convert the callbacks into async/await fashion in javascript in general? And in mongoose environment?
Would you be able to configure mongoose to use bluebird?
mongoose.Promise = require('bluebird');
Then you could get fancy and do:
User.remove().then(() => {
let admin = new User({ ... });
admin.save().then((admin) => {
}).catch((err) => {
//Saving admin error handling
});
}).catch((err) {
//User remove error handling
});
The same code you wrote above can be written as a promise. From the original docs,
"Mongoose queries are not promises. However, they do have a .then() function for yield and async/await. If you need a fully-fledged promise, use the .exec() function."
Thus you can use the async await pattern as follows,
async function removeUser () {
// Surround the following call with a try catch
await User.remove({});
}
EDIT:
One answer here says about plugging in blue bird promises. While this is a good approach, it is optional. You can do the same without plugging in your own Promise library.
You can use a Promise:
const userRemovePromise = new Promise((resolve, reject) => {
User.remove({}, (err) => {
if (err) reject();
let admin = new User({
email: 'admin#dap.com',
password: 'dapdap'
})
admin.save((err, admin) => {
if (err) reject();
resolve();
})
});
});
userRemovePromise.then(function(){
mongoose.disconnect();
});
As mongoose's default promise library mpromise is now deprecated so you can plugin your own promise library to make mongoose's operations work with await/async.
Mongoose queries are not promises. However, they do have a .then() function for yield and async/await. If you need a fully-fledged promise, use the .exec() function.
mongoose.Promise = require('bluebird'); bluebird promise
// mongoose.Promise = global.Promise; // default js promise - 4x- time slower than bluebird
async function removeUser () {
try {
await User.remove({}).exec();
let admin = new User({
email: 'admin#dap.com',
password: 'dapdap'
})
await admin.save();
} catch (e) {
console.log(e);
}
}
Official Doc: mongoosejs.com/docs/promises.html
Recent versions of Mongoose returns a Promise as well as providing the regular callback-style pattern. Since async functions are syntatic sugar over Promises, you can await calls to Mongoose methods.
async function clearUsers () {
try {
await User.remove({})
let admin = new User({
email: 'admin#dap.com',
password: 'dapdap'
})
await admin.save()
console.info('Success')
mongoose.disconnect()
} catch (e) {
console.error(e)
}
}
Some things to keep in mind:
async functions always returns a Promise. If you don't return anything from an async function, it will still return a Promise that resolves when execution of that function finishes, but will not resolve with any value.
try/catch in an async function works the same as for regular, synchronous code. If any function call in the try body throw or return a Promise that rejects, execution stops right on that line and proceeds to the catch body.
Rejected promises "trickle" up the function call chain. This means that the top-most callee can handle errors. See the below example:
This is an anti-pattern that should be avoided unless you absolutely need to handle an error in a specific function, possibly to provide a return value from a different source:
async function fn1 () {
throw new Error("Something went wrong")
}
async function fn2 () {
try {
await fn1()
} catch (e) {
throw e
}
}
async function fn3 () {
try {
await fn2()
} catch (e) {
throw e
}
}
async function run () {
try {
await fn3()
} catch (e) {
console.error(e)
}
}
The above could rather be implemented like the below and still catch the error, not resulting in a runtime panic/crash:
async function fn1 () {
throw new Error("Something went wrong")
}
function fn2 () {
return fn1()
}
function fn3 () {
return fn2()
}
async function run () {
try {
await fn3()
} catch (e) {
console.error(e)
}
}
There are multiple ways to write the above code that will all be valid, so I recommend that you explore these.
Keeping the above examples in mind, your function clearUsers() could then be rewritten to this:
async function clearUsers () {
await User.remove({})
let admin = new User({
email: 'admin#dap.com',
password: 'dapdap'
})
await admin.save()
mongoose.disconnect()
}
And then possibly called in two different ways;
By interacting with the Promise returned directly:
clearUsers()
.then(() => {
console.log('Success')
})
.catch((e) => {
console.error(e)
})
Or from within another async function:
(async function () {
try {
await clearUsers()
console.log('Success')
} catch (e) {
console.error(e)
}
})()
If any function call within the clearUsers() function throws, for example await admin.save(), execution will stop on that line and return a rejected Promise which will be caught in the corresponding catch blocks in both variants.
Using Bluebird is a good option to handle callbacks in promises, It makes it easier to understand to an extent.
However I would recommend you to try
Async.js
It is built particularly for managing asynchronous nature of javascript.
This particular function of the library would do exactly what you want.