3

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?

avatarhzh
  • 2,175
  • 4
  • 21
  • 32

2 Answers2

2

Simple 3 step process:

  1. Return a promise from the first .then call.

Change this:

// ...
const user = new Users({ username, password })
user.save()
// ...

to this:

// ...
const user = new Users({ username, password })
return user.save()
// ...

(Mind the return keyword, which will chain it with the second .then() call)


2. Reject the Promise in case existingUser returns false (thanks @JaromandaX for pointing out)

Change this:

if (existingUser) return res.status(422).send({ error: 'Username is in use' })

to this:

if (existingUser) {
    res.status(422).send({ error: 'Username is in use' });
    return Promise.reject('USER_EXISTS');
}

3. Drop the .then(onResolvedFunction, onRejectedFunction) pattern when possible, and use .catch(err) instead (to catch for a bigger spectrum of errors).

Delete the second argument from your .then()'s

,
err => next(err)

use a .catch instead:

Users.findOne({ username })
    .then(...)
    .then(...)
    .catch((e) => { // <-- Handle the error properly
         console.log(e);
         if (e !== 'USER_EXISTS')
             next(err);
     }); 

Mongoose Footnote!

This has nothing to do with promises. I see you named your model Users, but remember that, internally, Mongoose will pluralize your model names for you. You should either:

  • Name your model User; or

  • Explicitly set the pluralized form in a third argument, like this:

    const Users = mongoose.model('User', UserSchema, 'Users');

Community
  • 1
  • 1
Jose Lopez Garcia
  • 972
  • 1
  • 9
  • 21
  • the only issue here is that the `then( savedUser => res.send(...` code will run even in the case of `existingUser` being `true` - probably NOT what the OP wants .. also in the case of rejection in `findOne` the chained `.then` will also be executed – Jaromanda X Aug 04 '17 at 10:38
  • OP is using `.then(onResolvedFunction, onRejectedFunction)` pattern in his `.then`s - also, your code would execute `next(err)` in the case of existing user, the original code does not – Jaromanda X Aug 04 '17 at 10:54
1

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);
    });
});
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • I modified my answer accordingly. Do you think there is a need to add a second answer though? – Jose Lopez Garcia Aug 04 '17 at 10:53
  • yes, because this code behaves like the original in all cases, yours would not in the case of existing user - i.e. yours would still call `next(err)`, original code does not – Jaromanda X Aug 04 '17 at 10:55
  • Modified it to maximize the OP's understanding of the problem. Just mind how much work I put into the answer! Wish you a nice day and thanks for pointing out the errors I overlooked. – Jose Lopez Garcia Aug 04 '17 at 11:14
  • Only put a few seconds thought into my answer. Your welcome to point out any flaws if you can find them – Jaromanda X Aug 04 '17 at 12:46
  • @JoseLopezGarcia - do you want me to ask the OP to accept your answer instead? I don't post answers for points, but if it's that important to you, you can have the 15 – Jaromanda X Aug 04 '17 at 12:55
  • Oh no, please! Your answer is more straightforward mate than mine mate. That was not my intention. Happy coding! – Jose Lopez Garcia Aug 04 '17 at 14:40