Search code examples
javascriptecmascript-6es6-promise

Chaining ES6 promises without nesting


I'm trying to chain a second then method after the first one but it's not working correctly for some reason. It only works fine when I'm nesting the then method. Here's the code that doesn't work correctly:

auth.post('/signup', (req, res, next) => {

  const { username } = req.body
  const { password } = req.body

  Users.findOne({ username })
    .then(
      existingUser => {
        if (existingUser) return res.status(422).send({ error: 'Username is in use' })

        const user = new Users({ username, password })
        user.save()
      },
      err => next(err)
    )
    .then(
      savedUser => res.send({ 
        username: savedUser.username, 
        password: savedUser.password 
      }),
      err => next(err)
    )
})

Here when I post to '/signup' user gets saved into the database but I don't get the response with username and password. However:

auth.post('/signup', (req, res, next) => {

  const { username } = req.body
  const { password } = req.body

  Users.findOne({ username })
    .then(
      existingUser => {
        if (existingUser) return res.status(422).send({ error: 'Username is in use' })

        const user = new Users({ username, password })
        user.save()
        .then(
          savedUser => res.json({ 
            username: savedUser.username,
            password: savedUser.password
          }),
          err => next(err)
        )
      },
      err => next(err)
    )
})

This works as expected. user gets saved and I get the response with username and password. I've read that you can chain these then methods in a flat way without nesting. But I've checked questions on here and couldn't find an answer as to what I'm doing wrong here. Can someone please help with this issue?


Solution

  • You have at least three issues with your "chained" version

    1. You are not returning anything from your first .then
    2. in the case of existing user, the chained .then would still be executed
    3. in the case of an rejection in Users.findOne the chained .then would also be executed

    To fix:

    1. simply return .save()
    2. return a Promise.reject - alternatively you can throw an error
    3. don't use onRejected functions in .then, just have a single rejection handler, at the end of the chain, in a .catch

    I would chain that code like this:

    auth.post('/signup', (req, res, next) => {
    
        const { username } = req.body
        const { password } = req.body
    
        Users.findOne({ username })
        .then(existingUser => {
            if (existingUser) {
                return Promise.reject({
                    status:422,
                    error: 'Username is in use' 
                });
            }
            return new Users({ username, password }).save();
        })
        .then(savedUser => res.send({ 
            username: savedUser.username, 
            password: savedUser.password 
        }))
        .catch(err => {
            if (err.status) {
                return res.status(err.status).send({ error: err.error });
            }
            return next(err);
        });
    });