0

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: '[email protected]',
    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?

2
  • 2
    any sample code? Commented Feb 2, 2018 at 5:29
  • @zabusa I forgot to add the code. I have added it. Please check :) Commented Feb 2, 2018 at 5:30

6 Answers 6

1

You can use a Promise:

const userRemovePromise = new Promise((resolve, reject) => {
    User.remove({}, (err) => {
      if (err) reject();

      let admin = new User({
        email: '[email protected]',
        password: 'dapdap'
      })

      admin.save((err, admin) => {
        if (err) reject();

        resolve();
      })

    });

});

userRemovePromise.then(function(){  
    mongoose.disconnect();
});
Sign up to request clarification or add additional context in comments.

Comments

0

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

Comments

0

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.

Comments

0

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: '[email protected]',
      password: 'dapdap'
    })
    await admin.save();
    } catch (e) {
        console.log(e);
    }
}

Official Doc: mongoosejs.com/docs/promises.html

Comments

0

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: '[email protected]',
      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: '[email protected]',
    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.

Comments

-2

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.