Is using a promise inside another promise considered an anti-pattern? - javascript

I have a code that looks like this :
app.post("/api/exercise/add", function(req, res, next) {
User
.findById(req.body.userId)
.exec()
.then(user => user)
.then(function(user) {
let exercise = new Exercise({
description: req.body.description,
duration: req.body.duration,
date: req.body.date, //BUG: must add validations, date accepts 19984-01-01
user: user
})
.save()
.then(function(exercise) {
user.exercises.push(exercise)
user.
save().
then(user => res.json({ status: 201, exercises: user.exercises }))
})
.catch(err => next(err))
})
.catch(err => next(err));
});
Is the fact that I'm using a promise inside another promise, in this case, considered an anti-pattern?

In a sense, it's inelegant - the problem is that it creates unnecessary .then nesting. If the .then and .catch handlers that follow both Promises are the same, you can just return the new Promise inside the .then to pass it onto the next .then or .catch, as in the code below.
To pass along multiple variables / Promises to the next .then without reassigning an outer variable, use Promise.all:
app.post("/api/exercise/add", function(req, res, next) {
User
.findById(req.body.userId)
.exec()
.then(function(user) {
// return the Promise so it can be used by the next then, without nesting
// because you also need access to `user` in the next then, use Promise.all
return Promise.all([user, new Exercise({
description: req.body.description,
duration: req.body.duration,
date: req.body.date, //BUG: must add validations, date accepts 19984-01-01
user: user
})
.save()]);
})
.then(function([user, exercise]) {
user.exercises.push(exercise);
// return the Promise so it can be used by the next then, without nesting:
return user.save();
})
.then(user => res.json({ status: 201, exercises: user.exercises }))
.catch(err => next(err));
});
Note that the
.then(user => user)`
is completely superfluous - it doesn't do anything, you already have a Promise that resolves to the user you want in the next .then.

We can have something like this:
new Promise((resolve, reject) => {
let x = 25;
if (x%2 === 0) {
return Promise.resolve('even');
} else {
return Promise.resolve('odd');
}
})
.then(result => {
console.log('the number is '+result);
});
In this case, both the branches of the condition are homogeneous, they both return a string and the result is handled in the same way.
But this not always happens, for example:
new Promise((resolve, reject) => {
if (user.type === 'admin') {
return this.userService.getAdminTools();
} else {
return this.userService.getUserTools();
}
})
.then(result => {
// What type is the result? Maybe in this case, chaining is not the best solution!
});
If you have more branches and the result is not homogeneous, maybe chaining is not the best choice. You can listen to the Promise inside another Promise, or you can just call another method which contains the async code

Your execution flow is now separated into multiple branch which can be a wanted behavior.
When writing code, you should always think about re-usability and readability.
How an other programmer would read and understand my code easily and without getting an headache ?
The way you are putting it together is hard to follow. You should put the asynchronous action you want to perform into a separated function.
Breaking complex stuff into functions are a good practice to use in general, not only in this particular case. Try to have one function to do one thing, and have one execution flow.
User
.findById(req.body.userId)
.exec()
.then(user => user)
.then(user => asynchronousAddUser(user))
.catch(err => next(err));

It is not necessarily an antipattern, but it depends a lot on why are you doing it.
There may be valid reason for breaking the chain and starting a new one, but if you find yourself doing that too often then something is wrong and probably you should rethink your flow.
I see 2 common reasons why people tend to start a new chain
1.A handler at some point during the chain makes a decision based on a condition and each branch has a totally different way of doing its work. At this point is perfectly valid to start a new chain, but I would create a new method that returns a promise. The next handler in the chain must be aware about the fact that it may receive heterogenous data
NewPromise()
.then( res => {
if (someCond) {
return OtherPromise(args)
}
....
return obj
})
.then( res => {
//this promise must be aware that res may be heterogeneous
})
2.During the chain a handler receives some info that you can't easily propagate down the chain. For instance when needing 2 different piece of information that come from a DB and you need both in the end to do the work.
User.findById(uid1)
.then(user1 => {
return User.finById(uid2)
})
.then(user2 => {
// at this point user1 is not available any more
})
A solution for this is to have a variable outside of the chain and not start a new chain
var user1
User.findById(uid1)
.then(user => {
user1 = user
return User.finById(uid2)
})
.then(user2 => {
// at this point user is available and has the value of user1
})

Related

Promise chaining and conditionals

Pretty sure I could do this with async/await just fine, but I'd like to understand how to implement this logic without.
Workflow:
Find a job in the database
if the job exists, find a Person, if not, send a response to the frontend
then do Person logic
Code:
Job.findByPk(jobId)
.then(job => {
if(job) return Person.findOne(...)
else res.status(404).json(...)
})
.then(person => {
if(person)
// Do person logic here
})
.catch(err => ...);
The problem is that this logic obviously doesn't work. The person parameter in the second .then() block could be undefined if either no job or no person is found.
So a solution would be to do this in the first .then() block:
.then(job => {
if(job) // store the job locally
else res.status(404).json(...)
return Person.findOne(...)
})
but that means that the database is searched regardless of if a Job is found or not rather than being conditional on a job being found
How do structure this in a way that makes more sense?
Thanks
This is a lot simpler with await (assuming you make the parent function async):
try {
const job = await Job.findByPk(jobId);
if (!job) {
return res.status(404).json(...)
}
const person = await Person.findOne(...);
if (person) {
...
} else {
...
}
} catch(e) {
console.log(e);
res.sendStatus(500);
}
What makes this flow so much simpler is that all variables are in the same scope and you can return anywhere you want to finish the flow.
If you're going to stick with the previous .then() logic, then see this answer: How to chain and share prior results with promises for multiple different options.
You can simply add .thens within the first .then.
Job.findByPk(jobId)
.then(job => {
if(job)
return Person.findOne(...)
.then(person => {
if(person)
// Do person logic here
});
else res.status(404).json(...)
})
.catch(err => ...);
The selected answer is the best way of acheiving what I wanted - ie just using async/await - but for anyone who wants to know how I stuck with chaining, it's just breaking out of it properly. I ended up doing this by throwing an error (which is then handled elsewhere)
.then(job => {
if(!job) {
const error = new Error('Job not found');
error.statusCode(404);
throw error;
} else {
return Person.findOne(...)
}
})
.then(person => { // Person logic })
.catch(err => next(err))

How to propagate resolve() in nested promises?

I'm writing a React application and in particular cases I have to resolve nested promises. The code works fine but I can't propagate the resolve() function up to the outer level, thus I'm not able to get the returning value.
This is the code:
writeData(data) {
this.store.dispatch({type: "START_LOADER"})
return new Promise((resolve, reject) => {
this.manager.isDeviceConnected(this.deviceId).then(res => {
this.manager.startDeviceScan(null, null, (error, device) => {
if (device.id === this.deviceId) {
resolve("test") // -> this is propagate correctly
device.connect().then((device) => {
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
return device.discoverAllServicesAndCharacteristics()
}).then((device) => {
device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
).then(res => {
resolve("test2") // -> this is not propagated
}).catch(error => {
reject(error.message)
})
}).catch((error) => {
reject(error.message)
});
}
});
}).catch(error => {
reject(error.message)
})
})
}
...
...
async writeAsyncData(data) {
await this.writeData(data)
}
When I call this function:
this.writeAsyncData({data}).then(response => {
// here I expect whatever parameter I have passed to resolve()
console.log(response)
})
In case I leave resolve("test") uncommented I can console.log it without any problem, but if I comment it, the resolve("test2") doesn't show in console.log and response is undefined.
How can I make sure that even the nested parameter of the inner resolve reach the console.log ?
To nest promises properly, you do NOT wrap them in yet another manually created promise. That is an anti-pattern. Instead, you return the inner promises and that will then chain them. Whatever the inner-most promise returns will be the resolved value for the whole chain.
In addition, when you have any asynchronous operations that return callbacks, you must promisify them so that you are doing all your asynchronous control flow with promises and can consistently do proper error handling also. Do not mix plain callbacks with promises. The control flow and, in particular, proper error handling gets very, very difficult. One you start with promises, make all async operations use promises.
While this code is probably simplest with async/await, I'll first show you how you properly chain all your nested promises by returning every single inner promise.
And, to simplify your nested code, it can be flattened so that rather than each level of promise making deeper indentation, you can just return the promise back to the top level and continue processing there.
To summarize these recommendations:
1. Don't wrap existing promises in another manually created promise. That's a promise anti-pattern. Besides being unnecessary, it's very easy to make mistakes with proper error handling and error propagation.
2. Promisify any plain callbacks. This lets you do all your control flow with promises which makes it a lot easier to avoid errors or tricky situations where you don't know how to propagate errors properly.
3. Return all inner promises from within the .then() handlers to properly chain them together. This allows the inner-most return value to be the resolved value of the whole promise chain. It also allows errors to properly propagate all the way up the chain.
4. Flatten the chain. If you have multiple promises chained together, flatten them so you are always returning back to the top level and not creating deeper and deeper nesting. One case where you do have to make things deeper is if you have conditionals in your promise chain (which you do not have here).
Here's your code with those recommendations applied:
// Note: I added a timeout here so it will reject
// if this.deviceId is never found
// to avoid a situation where this promise would never resolve or reject
// This would be better if startDeviceScan() could communicate back when
// it is done with the scan
findDevice(timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error("findDevice hit timeout before finding match device.id"));
}, timeout);
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
reject(error);
clearTimeout(timer);
return
}
if (device.id === this.deviceId) {
resolve(device);
clearTimeout(timer);
}
});
});
}
writeData(data) {
this.store.dispatch({type: "START_LOADER"});
return this.manager.isDeviceConnected(this.deviceId).then(res => {
return this.findDevice();
}).then(device => {
return device.connect();
}).then(device => {
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
return device.discoverAllServicesAndCharacteristics();
}).then(device => {
return device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
);
}).then(res => {
return "test2"; // this will be propagated
});
}
Here's a version using async/await:
findDevice(timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error("findDevice hit timeout before finding match device.id"));
}, timeout);
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
reject(error);
clearTimeout(timer);
return
}
if (device.id === this.deviceId) {
resolve(device);
clearTimeout(timer);
}
});
});
}
async writeData(data) {
this.store.dispatch({type: "START_LOADER"});
let res = await this.manager.isDeviceConnected(this.deviceId);
let deviceA = await this.findDevice();
let device = await deviceA.connect();
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
await device.discoverAllServicesAndCharacteristics();
let res = await device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
);
return "something"; // final resolved value
}
Note: In your original code, you have two overriding definitions for device. I left that there in the first version of the code here, but changed the first one to deviceA in the second one.
Note: As your code was written, if this.manager.startDeviceScan() never finds a matching device where device.id === this.deviceId, your code will get stuck, never resolving or rejecting. This seems like a hard to find bug waiting to happen. In the absolute worst case, it should have a timeout that would reject if never found, but probably the implementation of startDeviceScan needs to communicate back when the scan is done so the outer code can reject if no matching device found.
Note: I see that you never use the resolved value from this.manager.isDeviceConnected(this.deviceId);. Why is that? Does it reject if the device is not connected. If not, this seems like a no-op (doesn't do anything useful).
Note: You call and wait for device.discoverAllServicesAndCharacteristics();, but you never use any result from it. Why is that?

Understanding explicit promise construction anti pattern

CertainPerformance highlighted in my previous post advised me to avoid the explicit Promise construction antipattern with reference to to following question in stackoverflow
Frankly, Speaking, I am new to JS and node and I haven't used promise a lot. I went and read those article but either I was unable to comprehend or unable to relate or maybe somewhere my understanding of promises have been vague/wrong all together
So I decided to ask this question in a new thread and seek for help.
So what am I doing and why am I doing it
I am creating helper/common function which I could use to keep my code tidy and if in case I want to change anything inside function at anytime, I don't have to manually change every function.
So these are the functions I have made
//Find user by email Address
const findUserByEmail = (emailAddress) => {
return new Promise((resolve, reject) => {
User.findOne({email: emailAddress}).then(response => {
resolve(res)
}).catch(error => {
reject("Error in findUserByEmail", error);
})
})
}
//Create User
const createNewUser = (newUserDetails) => {
return new Promise((resolve, reject) => {
new User({
fullName: newUserDetails.fullName,
email: newUserDetails.email,
image: newUserDetails.image,
gender: newUserDetails.gender,
age: newUserDetails.age
}).save().then((response) => {
resolve(response)
}).catch((error) => {
reject("Problem in Creating New User", error)
})
})
}
Question 1
Now, I am assuming CertainPerformance said the excessive use of promises because I am creating new promise return new Promise((resolve, reject) => { when I am already using promises with mongoose User.findOne({email: emailAddress}).then(response => { ?
But the reason for me to create those promise was, when I call these helper function from anywhere in my app after importing
const { findUserByEmail } = require("./my_db_query");
I would probably want it return a response or throw an error in case of error
findUserByEmail("test#example.com").then(/*...*/).catch(/*...*/);
If I change my above code snippet without adding new promise
function findUserByEmail (email) {
return User.findOne({email: email}).then(currentUser => currentUser).catch(error => error)
}
Question 2
Then I won't probably be able to .then and .catch in findUserByEmail("test#example.com")?
And In API route of App, where I would be calling the findUserByEmail("test#example.com") function, I would want to do something else if there is an error (which would be different for different case and hence I cannot use it in my helper function).
Question 3
Does, it make sense now for doing return new Promise((resolve, reject) => { instead of doing just one return User.findOne( or am I missing something?
Because .findOne already returns a Promise, there's no need to construct a new one with new Promise - instead, just chain onto the existing Promise chain with .then and .catch as needed. Such Promise chains can have any number of .thens and .catchs - just because you consume a Promise with one .then doesn't prevent you from using the same resolve value elsewhere. To illustrate:
makePromise()
.then((result) => {
console.log(result);
// Returning inside a `.then` will pass along the value to the next `.then`:
return result;
})
.then((result) => {
// this `result` will be the same as the one above
});
In other words - there's no need to construct a new Promise every time you want to be able to use another .then. So:
Then I won't probably be able to .then and .catch in findUserByEmail("test#example.com")
isn't correct - you can indeed chain onto the end of an existing Promise with as many .thens and .catches as you want.
Note that a .then which only returns its parameter and does nothing else (such as .then(currentUser => currentUser)) is superfluous - it won't do anything at all. Also note that a .catch will catch Promise rejections and resolve to a resolved Promise. So if you do
function findUserByEmail(email) {
return User.findOne({email: email})
.then(currentUser => currentUser)
.catch(error => error)
}
that catch means that callers of findUserByEmail will not be able to catch errors, because any possible errors were caught in findUserByEmail's catch. Usually, it's a good idea to allow errors to percolate up to the caller of the function, that way you could, for example:
someFunctionThatReturnsPromise('foobar')
.then((result) => {
// everything is normal, send the result
res.send(result);
})
.catch((err) => {
// there was an error, set response status code to 500:
res.status(500).send('there was an error');
})
So, unless your findUserByEmail or createNewUser helper functions need to do something specific when there's an error, it would probably be best just to return the Promise alone:
const findUserByEmail = email => User.findOne(email);
const createNewUser = newUserDetails => new User(newUserDetails).save();
If your helper functions do need to do something when there's an error, then to make sure that the error gets passed along properly to the caller of the function, I'd recommend either throwing the error inside the catch:
const findUserByEmail = email => User.findOne(email)
.catch((err) => {
// error handling - save error text somewhere, do a console.log, etc
throw err;
});
so that you can catch when something else calls findUserByEmail. Otherwise, if you do something like
const findUserByEmail = email => User.findOne(email)
.catch((err) => {
// do something with err
return err;
});
then the caller of findUserByEmail will have to check inside the .then if the result is actually an error, which is weird:
findUserByEmail('foo#bar.com')
.then((result) => {
if (result instanceof Error) {
// do something
} else {
// No errors
}
});
Better to throw the error in findUserByEmail's catch, so that the consumer of findUserByEmail can also .catch.
It never makes sense to create a promise with promise constructor when there's existing promise, that's why it's called promise construction antipattern.
This is a mistake, reject("Error in findUserByEmail", error). reject accepts only 1
argument, which is rejection reason. error will be ignored. It's conventionanl for an error to be Error object and not a string.
The function may be refactored to:
const findUserByEmail = (emailAddress) => {
return User.findOne({email: emailAddress})
.then(response => response) // noop
.catch(error => {
const readableError = new Error('Error in findUserByEmail');
readableError.originalError = error;
throw readableError;
});
})
}
etc.
Antipatterns don't necessary result in bad performance but they result in code smell. They make the code harder to read, maintain and test, also show that a developer may have a poor understanding of the subject.
Promise constructor has some insignificant performance impact. It introduces another level of nesting and contributes to callback hell - promises are supposed to help avoiding it.
If I change my above code snippet without adding new promise <...>
Then I won't probably be able to .then and .catch in findUserByEmail("test#example.com")?
No, a promise can be chained with then(...) and catch(...) (which is syntactic sugar for then(null, ...)) as many times as needed, that's the strong side of the pattern. Notice that catch(err => { return err }) and catch(err => { throw err }) is not the same thing, the former catches an error, the latter rethrows it.

What the right way to use Promises with mongoose?

I'm new with Promises and in two days a already saw at least five ways to do.
Some ways are older versions, others focus on other languages, so I decide to ask what should be used today.
I did it this way, with 'err' and 'doc' parameters:
Proposals.findById({ ...body, user }, (err, doc) =>
Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: doc._id}}
)
)
.then((Proposals) => Proposals.view())
.then(success(res, 201))
.catch(next)
And this:
Proposals.create({ ...body, user })
.then((Proposals) => {
Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: Proposals._id}}
)
.catch(Advertisements)
return Proposals.view()
})
.then(success(res, 201))
.catch(next)
Without the "catch" and "return" itsn't work, but I don't know why exactly. :x
The answer to
Using Promises in Mongoose Routes
seems like a beautiful but I couldn't reproduce in code above.
return findUser(userId)
.then((user) => findReceipt(user.bookName))
.then((receipt) => res.status(200).json(receipt))
.catch((err) => res.status(500).json(null))
Are any of these codes more right?
Could you help me to fix the second code?
Thank you.
Let me detail the syntax of the first block of code:
Proposals.findById({ ...body, user }, (err, doc) => {
return Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: doc._id}}
);
})
.then((Proposals) => { return Proposals.view() })
.then(success(res, 201))
.catch(next)
In ES6 syntax, this function:
() => ('test');
Equals to
() => { return 'test' };
So I believe your main problem is because of not understanding exactly the ES6 syntax at the beginning.
You should follow these best practices when it comes to Promises:
Avoid nesting catch in Promises
Chain Promises by returning them and use them in the then
Hope it helps.
There are number of ways to handle promises in Mongoose. The exact way that you decide to implement it is left to personal preference, but you need to make sure that your order of execution is as expected. In your first example you are mixing a callback with promises, which I personally think is not optimal. The third example is cleaner as it is a chain of promises being passed down via the then calls.
The important thing to note is that if you want to run async code inside your then calls, you need to ensure you return a Promise that resolves at the right moment. In your second example, you are possibly returning before your findOneAndUpdate call is finished executing. You can avoid this in a number of ways. One option would be to wrap the second call inside a Promise. I give you an idea below by mixing parts of your second and third examples and assuming that the return value from proposal.view() is not async. I use explicit return statements for clarity.
Proposals.create({ ...body, user })
.then((proposal) => {
// call returns a new promise
return new Promise((resolve, reject) => {
// make second async call to database
Advertisements.findOneAndUpdate(
{ _id: body.advertisement },
{ $push: { proposals: proposal._id } },
)
.then(() => {
// promise resolves here and passes return
// value of proposal.view() to next `then`
return resolve(proposal.view())
});
});
})
// consume the value from proposal.view() and send it in response
.then(proposal => res.status(200).json(proposal))
.catch(next)
This would mean that the return value of proposal.view() will only be passed onto the next then once the findOneAndUpdate call is complete.
The example code is not waiting for, or using the result of the update.
In promises returning a promise and using .then waits for that async operation. You can then check the result if required:
Proposals.create({ ...body, user })
.then((proposal) => {
return Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: proposal._id}}
)
})
.then((update) => {
if (!update) throw new Error(`Could not find advertisement for "${user}"`)
success(res, 201))
})
.catch(next)
Or the equivalent ES2017 code using await to wait for the promise to resolve, which should help reason how the above Promise .then code flow works.
async function handler(){
try {
let proposal = await Proposals.create({ ...body, user })
let update = await Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: proposal._id}}
)
if (!update) throw new Error(`Could not find advertisement for "${user}"`)
success(res, 201)
}
catch (err) {
next(err)
}
}

Javascript - can't resolve this 'warning: promise was created in a handler'

Using sequelize.js in a nodejs app, and I have a promise.all that takes two promises (a user query, and a color query):
router.get(`/someEndPoint`, (req, res) => {
let userAccount = user.findOne({
where: {
id: //some ID
}
});
let colorStuff = color.findOne({
where: {
colorName: //some color
}
})
Promise.all([userAccount , colorStuff ]).then(([result1, result2]) => {
//do stuff, such as:
res.send('success');
}).catch(err => {
console.log(err)
});
});
At the part that says //do stuff, my console keeps giving me this warning:
a promise was created in a handler at... but was not returned from it,
see (URL that I can't post) at Function.Promise.attempt.Promise.try
I'm not sure how to resolve this. I thought after the .then that the promises are resolved?
Hard to tell without other context, but perhaps you need to return the Promise.all
return Promise.all([user, color])...
From the bluebird docs here: https://github.com/petkaantonov/bluebird/blob/master/docs/docs/warning-explanations.md#warning-a-promise-was-created-in-a-handler-but-was-not-returned-from-it
if there are any other promises created in the // do stuff area, be sure to return those as well.

Categories

Resources