ES6 promises chaining without nesting
I am trying to link the second method after the first, but it doesn't work correctly for some reason. It only works well when I nest 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 the message '/signup'
user
, it is saved in the database, but I am not getting a 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
is saved and I get a response with username and password. I read that you can bind these methods flat without nesting. But I checked the questions here and couldn't find an answer as to what I am doing wrong here. Can anyone help with this problem?
source to share
You have at least three problems with your chained version
- You don't return anything from your first
.then
- in case of an existing user, the chain
.then
will run - in case of deviation in, the
Users.findOne
chain will also be executed.then
To fix it:
- just return
.save()
- return a
Promise.reject
- alternatively you canthrow
run the error - don't use functions
onRejected
in.then
, you just have one rejection handler at the end of the chain in.catch
I would link this 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);
});
});
source to share
A simple 3 step process:
- Return a promise from the first call
.then
.
Change this:
// ...
const user = new Users({ username, password })
user.save()
// ...
:
// ...
const user = new Users({ username, password })
return user.save()
// ...
(note the return keyword, which will link it to the second call .then()
)
2. Reject the promise in case it existingUser
returns false
(thanks @JaromandaX for pointing out)
Change this:
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
:
if (existingUser) {
res.status(422).send({ error: 'Username is in use' });
return Promise.reject('USER_EXISTS');
}
3. If possible, clear the pattern.then(onResolvedFunction, onRejectedFunction)
and use .catch (err) instead (to catch a wider range of errors).
Remove the second argument from <.then()
, err => next(err)
use instead of .catch:
Users.findOne({ username })
.then(...)
.then(...)
.catch((e) => { // <-- Handle the error properly
console.log(e);
if (e !== 'USER_EXISTS')
next(err);
});
Mongoose Brief!
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 must either:
- Name your model
User
; or -
Explicitly set the plural form in the third argument, for example:
const Users = mongoose.model('User', UserSchema, 'Users');
source to share