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.
Related
I'm using crypto.generateKeyPair inside a post endpoint in Express.
I need to insert the key generated inside my DB and then return within the endpoint the id row of the inserted row.
The endpoint code reads as:
app.post('/createKeys', (req, res) => {
crypto.generateKeyPair('rsa',,
(err, publicKey, privateKey) => {
if(!err) {
let id = myfunction(publicKey.toString('hex'),
privateKey.toString('hex'));
console.log(id)
} else {
res.status(500).send(err);
}
});
});
async function myfunction(publicKey, privateKey) {
await pool.query('INSERT INTO users (publickey, privatekey) VALUES ($1, $2) RETURNING id',
[publicKey, privateKey],
(error, results) => {
if (error) {
throw error;
}
resolve(results.rows[0]['id']);
});
};
However, inside the callback in crypto I get only a Promise, or undefined if I don't use async/await. How can I await myfunction result so I can send back to the user the id?
Several issues here:
await only does something useful when you are awaiting a promise.
pool.query() does not return a promise when you pass it a callback, so your await there is not doing anything useful.
resolve() is not a function that exists outside the context of creating a new promise with new Promise((resolve, reject) => { code here })
throw error inside an asynchronous callback will not do anything useful as there is no way to catch that exception and thus no way to implement any decent error handling. Don't write code that way. When you promisify the function (as shown below), you can then reject the promise and that will offer a way to propagate the error back to the caller.
Your choices for waiting for pool.query() with await here are:
Use the version of your database that natively supports promises and then don't pass a callback to pool.query() so that it returns a promise that tells you when it's complete.
Promisify your own function by wrapping pool.query() in a new promise and call resolve() and reject() appropriately.
Remember, do NOT mix plain callbacks and promise. Instead, promisify any asynchronous functions that use plain callbacks and then do all your logic flow with promises.
Here's a manually promisified version of your myfunction():
function myfunction(publicKey, privateKey) {
return new Promise((resolve, reject) => {
pool.query('INSERT INTO users (publickey, privatekey) VALUES ($1, $2) RETURNING id',
[publicKey, privateKey],
(error, results) => {
if (error) {
reject(error);
return;
}
resolve(results.rows[0]['id']);
});
});
}
crypto.generateKeyPairP = util.promisify(crypto.generateKeyPair);
app.post('/createKeys', async (req, res) => {
try {
const {publicKey, privateKey } = await crypto.generateKeyPairP('rsa');
const id = await myfunction(publicKey.toString('hex'), privateKey.toString('hex'));
console.log(id);
// send your response here, whatever you want it to be
res.send(id);
} catch(e) {
res.status(500).send(e);
}
});
Note that in this implementation, the resolve and reject functions come from the new Promise() - they don't just exist in this air.
But, there is likely a version of your database or an interface in your existing database module where pool.query() can return a promise directly using built-in promise support.
I am not so into RxJS and I am finding some problems to understand this piece of code retrieved into an Angular project on which I am working.
First of all into a component TypeScript code I have this method:
async post(): Promise<void> {
this.submitted.next(true);
try {
await this.setAddress();
this.activeModal.close();
} catch (e) {
console.error('Storage upload error', e);
this.submitted.next(false);
}
}
As you can see this method have async prefix because into the try block it contains these 2 rows:
await this.setAddress();
this.activeModal.close();
from what I have understand (please correct me if I am doing wrong assertion) basically the await in front of this.setAddress() it means: await that this method call end, when it is completed executed the following operation (that in this case close a modal window).
From what I have understand it replave the then() method handling a Promise resolution. Is it correct or not?
So my doubt is: have my setAddress() method return a Promise? In my specific case setAddress() method is used to call a service method saving some data on the database and have this code:
async setAddress(): Promise<void> {
try {
const c: Address = {
companyName:this.addressFormGroup.get('companyName').value,
street: this.addressFormGroup.get('street').value,
city: this.addressFormGroup.get('city').value,
zipCode: this.addressFormGroup.get('zipCode').value,
notes: this.addressFormGroup.get('notes').value,
};
//save/update record
await this.userService.setUserAdresss(this.currentUserUID,this.addressType,c);
this.success = true;
if (!this.isEditMode) {
this.addressFormGroup.reset();
}
} catch (e) {
console.error(e);
} finally {
this.submitted.next(false);
}
}
And here I have a lot of doubts on how it works...ok the method signature:
async setAddress(): Promise<void> {
seems to return a Promise (why ? what it means?). But where is it effectivelly returning a Promise? In the code of this method I can't find that it is returning a Promise nowhere. It seems to me that it is returning nothing because it is not containing the return statement !!!
My only interpretation is the following one (but it is my idea and probably totally wrong): also if it is not explicitly returning nothing it have a Promise as method returned type. So it means that at the end of the method execution TypeScript automatically return an "empty" Promise that simply means: "method execution completed".
I am absolutly not sure, this is the only explaination that I can give to this code.
How it wxactly works?
Your assumptions are correct.
A function declared with the async keyword will return a Promise that is completed when the function has finished its execution.
The await keyword is equivalent to using the then method with the remaining lines of codes as the callback function.
Using try/catch/finally arround an await is equivalent to using the catch/finally method on the promise.
This is your code written with promises instead of async/await :
post(): Promise<void> {
this.submitted.next(true);
return this.setAddress()
.then(() => this.activeModal.close())
.catch((e) => {
console.error('Storage upload error', e);
this.submitted.next(false);
});
}
setAddress(): Promise<void> {
const c: Address = {
companyName:this.addressFormGroup.get('companyName').value,
street: this.addressFormGroup.get('street').value,
city: this.addressFormGroup.get('city').value,
zipCode: this.addressFormGroup.get('zipCode').value,
notes: this.addressFormGroup.get('notes').value,
};
//save/update record
return this.userService.setUserAdresss(this.currentUserUID,this.addressType,c)
.then(() => {
this.success = true;
if (!this.isEditMode) {
this.addressFormGroup.reset();
}
})
.catch((e) => console.error(e))
.finally(() => this.submitted.next(false));;
}
Introduction
Hey there,
I am trying to pass out data from the mongoose withTransaction callback. Right now, I am using the following code which implements callbacks:
const transactionSession = await mongoose.startSession()
await transactionSession.withTransaction(async (tSession) => {
try {
// MARK Transaction writes & reads removed for brevity
console.log("Successfully performed transaction!")
cb(null, "Any test data")
return Promise.resolve()
} catch (error) {
console.log("Transaction aborted due to error:", error)
cb(error)
return Promise.reject()
}
})
} catch (error) {
console.log(error)
return cb(error)
}
A more detailed snippet of the withTransaction helper in use can be found here.
A link to the official Mongoose documentation regarding the withTransaction helper can be found here.
At the moment, I am using a callback to pass out data from the withTransactioncallback:
cb(null, "Any test data")
However, the problem is that naturally the callback is executed first, before the Promise.resolve() is returned. This means, that (in my case) a success response is sent back to the client before any necessary database writes are committed:
// this is executed first - the callback will send back a response to the client
cb(null, "Any test data")
// only now, after the response already got sent to the client, the transaction is committed.
return Promise.resolve()
Why I think this is a problem:
Honestly, I am not sure. It just doesn't feel right to send back a success-response to the client, if there hasn't been any database write at that time. Does anybody know the appropriate way to deal with this specific use-case?
I thought about passing data out of the withTransaction helper using something like this:
const transactionResult = await transactionSession.withTransaction({...})
I've tried it, and the response is a CommandResult of MongoDB, which does not include any of the data I included in the resolved promise.
Summary
Is it a problem, if a success response is sent back to the client before the transaction is committed? If so, what is the appropriate way to pass out data from the withTransaction helper and thereby committing the transaction before sending back a response?
I would be thankful for any advice I get.
It looks like there is some confusion here as to how to correctly use Promises, on several levels.
Callback and Promise are being used incorrectly
If the function is supposed to accept a callback, don't return a Promise. If the function is supposed to return a Promise, use the callback given by the Promise:
const transactionSession = await mongoose.startSession()
await transactionSession.withTransaction( (tSession) => {
return new Promise( (resolve, reject) => {
//using Node-style callback
doSomethingAsync( (err, testData) => {
if(err) {
reject(err);
} else {
resolve(testData); //this is the equivalent of cb(null, "Any test data")
}
});
})
Let's look at this in more detail:
return new Promise( (resolve, reject) => { This creates a new Promise, and the Promise is giving you two callbacks to use. resolve is a callback to indicate success. You pass it the object you'd like to return. Note that I've removed the async keyword (more on this later).
For example:
const a = new Promise( (resolve, reject) => resolve(5) );
a.then( (result) => result == 5 ); //true
(err, testData) => { This function is used to map the Node-style cb(err, result) to the Promise's callbacks.
Try/catch are being used incorrectly.
Try/catch can only be used for synchronous statements. Let's compare a synchronous call, a Node-style (i.e. cb(err, result)) asynchronous callback, a Promise, and using await:
Synchronous:
try {
let a = doSomethingSync();
} catch(err) {
handle(err);
}
Async:
doSomethingAsync( (err, result) => {
if (err) {
handle(err);
} else {
let a = result;
}
});
Promise:
doSomethingPromisified()
.then( (result) => {
let a = result;
})
.catch( (err) => {
handle(err);
});
Await. Await can be used with any function that returns a Promise, and lets you handle the code as if it were synchronous:
try {
let a = await doSomethingPromisified();
} catch(err) {
handle(err);
}
Additional Info
Promise.resolve()
Promise.resolve() creates a new Promise and resolves that Promise with an undefined value. This is shorthand for:
new Promise( (resolve, reject) => resolve(undefined) );
The callback equivalent of this would be:
cb(err, undefined);
async
async goes with await. If you are using await in a function, that function must be declared to be async.
Just as await unwraps a Promise (resolve into a value, and reject into an exception), async wraps code into a Promise. A return value statement gets translated into Promise.resolve(value), and a thrown exception throw e gets translated into Promise.reject(e).
Consider the following code
async () => {
return doSomethingSync();
}
The code above is equivalent to this:
() => {
const p = new Promise(resolve, reject);
try {
const value = doSomethingSync();
p.resolve(value);
} catch(e) {
p.reject(e);
}
return p;
}
If you call either of the above functions without await, you will get back a Promise. If you await either of them, you will be returned a value, or an exception will be thrown.
I'm learning on how to use Async/Await, and as far as I know it should wait until Await is done with executing and then go on with the code.
However, my code completely stops executing after my Await, this is my method (I'm using Vue):
async register () {
await this.form.validateFields((err, values) => {
if (err) {
return
}
alert('this gets executed if err is empty')
})
alert('this never gets get executed, why?')
}
validateFields() is a function of Vue's version of Ant Design, and validates my form input. More about it here, maybe it will help inform any of you.
So what exactly am I doing wrong here?
Async await exists to simplify the syntax for working with promises. If you aren't using promises, then async/await gains you nothing.
It looks like validateFields is using callbacks instead of promises, so you'll need to create your own promise around it. For example:
async register () {
try {
const values = await new Promise((resolve, reject) => {
this.form.validateFields((err, values) => {
if (err) {
reject (err);
} else {
resolve(values);
}
})
})
console.log('got values', values);
} catch (error) {
console.log('caught an error', error);
}
}
I'm trying to handle a custom error that my async method throws, but the try catch block doesn't work appropriately.
I think the way I'm doing it should work but the error is not caught and the program terminates by displaying it in the terminal.
Here is where it throws the error:
async setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
await model.findOneAndUpdate({ id: tap.id }, tap, (err, result) => {
let error = null;
if (!result) {
throw new Error('Tap doesn\'t exists', 404);
}
return result;
});
}
Then, the error handling code:
async setupTapHandler(request, h) {
const tapData = {
id: request.params.id,
clientId: request.payload.clientId,
beerId: request.payload.beerId,
kegId: request.payload.kegId,
};
try {
await this.kegeratorApi.setupTap(tapData);
} catch (e) {
if (e.code === 404) return h.response().code(404);
}
return h.response().code(204);
}
Can someone help me?
I also looked at other topics:
Correct Try...Catch Syntax Using Async/Await
How to properly implement error handling in async/await case
You can only use await to successfully wait on an async operation if you are awaiting a promise. Assuming you are using mongoose, I don't know mongoose really well, but it appears that model.findOneAndUpdate() does not return a promise if you pass it a callback. Instead, it executes and puts the result in the callback.
In addition, doing a throw from a callback like this just throws into the database (the code that called the callback) and won't do you any good at all. To have a throw make a rejected promise, you need to either be throwing from the top level of an async function or be throwing from inside a .then() or .catch() handler or inside a promise executor function. That's where throw makes a promise rejected.
The key here is that you want to use the promise interface to your database, not the callback interface. If you don't pass a callback, then it returns a query which you can use .exec() on to get a promise which you can then use with await.
In addition, you weren't building an error object that would have a .code property set to 404. That isn't a property that is supported by the Error object constructor, so if you want that property, you have to set it manually.
I'd suggest this:
async setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
let result = await model.findOneAndUpdate({ id: tap.id }, tap).exec();
if (!result) {
let err = new Error('Tap doesn\'t exists');
err.code = 404;
throw err;
}
return result;
}
Or, with only one async operation here, there's really not much benefit to using await. You could just do this:
setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
return model.findOneAndUpdate({ id: tap.id }, tap).exec().then(result => {
if (!result) {
let err = new Error('Tap doesn\'t exists');
err.code = 404;
throw err;
}
return result;
});
}
The function findOneAndUpdate returns a promise so there should be no need for the callback. If a callback is needed and you can't update to a newer version then maybe wrap calls in a promise (under To use a callback api as promise you can do:)
Then you want to set code on error, you can't do that with the constructor.
async setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
const result = await model.findOneAndUpdate({ id: tap.id }, tap);
if (!result) {
const e = new Error('Tap doesn\'t exists');
e.code = 404;
throw(e);
}
return result;
}
async setupTapHandler(request, h) {
const tapData = {
id: request.params.id,
clientId: request.payload.clientId,
beerId: request.payload.beerId,
kegId: request.payload.kegId,
};
try {
await this.kegeratorApi.setupTap(tapData);
} catch (e) {
if (e.code === 404) return h.response().code(404);
}
return h.response().code(204);
}