Promise { <pending> } error even after reaching to then() block [duplicate] - node.js

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I am working on a small shopping site using nodejs and mongodb. I have been able to store and retrieve data from my database. However, I can't get this particular function to work which is supposed to retrieve products from user cart. The products are retrieved into the then() block, but when i try to return or print the products by performing some operations in the products, i get output as Promise { pending }.
Before marking this question as duplicate(which it is not, but if you think it is), atleast help me with solve this.
const productIds = this.cart.items.map(eachItem => {
return eachItem.pid;
}); //to get product IDs of all the products from the cart
const cartData = db.collection('products') //db is the database function and works fine
.find({_id: {$in: productIds}})
.toArray()
.then(products => {
console.log(products); //all the products are printed correctly
products.map(eachProduct => {
return {
...eachProduct,
quantity: this.cart.items.find(eachCP => {
return eachCP.pid.toString() === eachProduct._id.toString()
}).quantity //to get the quantity of that specific product (here eachProduct)
};
})
})
.catch(err => {
console.log(err);
});
console.log('cartData: ', cartData); //but here it prints Promise /{ pending /}
I can't understand why i get Promise { } as output although i get the data from the database successfully in the then() block.
Sorry for the messsy code btw. I'm new to mongodb and don't have so much knowledge about promises too.

Promise#then does not "wait" in the sense that the next statement in the program would be delayed until the promise completes.
It "waits" only in the sense that execution of the callback that you pass to then is delayed until the promise completes.
But your current function (the one that set up the then) does not block and continues to run immediately. Meaning that everything outside of the function you pass to then may see the promise in its still incompleted state.
You probably want to make use of the async/await construct, as described (for example) in the linked duplicate thread.

Related

Node.js .map function causing express server to return the response object early

My goal is to create an abstracted POST function for an express running on Node similar to Django's inbuilt REST methods. As a part of my abstracted POST function I'm checking the database (mongo) for valid foreign keys, duplicates, etc..., which is where we are in the process here. The function below is the one that actually makes the first calls to mongo to check that the incoming foreign keys actually exist in the tables/collections that they should exist in.
In short, the inbuilt response functionality inside the native .map function seems to be causing an early return from the called/subsidiary functions, and yet still continuing on inside the called/subsidiary functions after the early return happens.
Here is the code:
const db = require(`../../models`)
const findInDB_by_id = async params => {
console.log(`querying db`)
const records = await Promise.all(Object.keys(params).map(function(table){
return db[table].find({
_id: {
$in: params[table]._ids
}
})
}))
console.log('done querying the db')
// do stuff with records
return records
}
// call await findIndDB_by_id and do other stuff
// eventually return the response object
And here are the server logs
querying db
POST <route> <status code> 49.810 ms - 9 //<- and this is the appropriate response
done querying the db
... other stuff
When I the function is modified so that the map function doesn't return anything, (a) it doesn't query the database, and (b) doesn't return the express response object early. So by modifying this:
const records = await Promise.all(Object.keys(params).map(function(table){
return db[table].find({ // going to delete the `return` command here
_id: {
$in: params[table]._ids
}
})
}))
to this
const records = await Promise.all(Object.keys(params).map(function(table){
db[table].find({ // not returning this out of map
_id: {
$in: params[table]._ids
}
})
}))
the server logs change to:
querying db
done querying the db
... other stuff
POST <route> <status code> 49.810 ms - 9 // <-appropriate reponse
But then I'm not actually building my query, so the query response is empty. I'm experiencing this behavior with the anonymous map function being an arrow function of this format .map(table => (...)) as well.
Any ideas what's going on, why, or suggestions?
Your first version of your code is working as expected and the logs are as expected.
All async function return a promise. So findInDB_by_id() will always return a promise. In fact, they return a promise as soon as the first await inside the function is encountered as the calling code continues to run. The calling code itself needs to use await or .then() to get the resolved value from that promise.
Then, some time later, when you do a return someValue in that function, then someValue becomes the resolved value of that promise and that promise will resolve, allowing the calling code to be notified via await or .then() that the final result is now ready.
await only suspends execution of the async function that it is in. It does not cause the caller to be blocked at all. The caller still has to to deal with a promise returned from the async function.
The second version of your code just runs the db queries open loop with no control and no ability to collect results or process errors. This is often referred to as "fire and forget". The rest of your code continues to execute without regard for anything happening in those db queries. It's highly unlikely that the second version of code is correct. While there are a very few situations where "fire and forget" is appropriate, I will always want to at least log errors in such a situation, but since it appears you want results, this cannot be correct
You should try something like as when it encounter first async function it start executing rest of the code
let promiseArr = []
Object.keys(params).map(function(table){
promiseArr.push(db[table].find({
_id: {
$in: params[table]._ids
}
}))
})
let [records] = await Promise.all(promiseArr)
and if you still want to use map approach
await Promise.all(Object.keys(params).map(async function(table){
return await db[table].find({
_id: {
$in: params[table]._ids
}
})
})
)

Updating a user concurrently in mongoose using model.save() throws error

I am getting a mongoose error when I attempt to update a user field multiple times.
What I want to achieve is to update that user based on some conditions after making an API call to an external resource.
From what I observe, I am hitting both conditions at the same time in the processUser() function
hence, user.save() is getting called almost concurrently and mongoose is not happy about that throwing me this error:
MongooseError [ParallelSaveError]: Can't save() the same doc multiple times in parallel. Document: 5ea1c634c5d4455d76fa4996
I know am guilty and my code is the culprit here because I am a novice. But is there any way I can achieve my desired result without hitting this error? Thanks.
function getLikes(){
var users = [user1, user2, ...userN]
users.forEach((user) => {
processUser(user)
})
}
async function processUser(user){
var result = await makeAPICall(user.url)
// I want to update the user based on the returned value from this call
// I am updating the user using `mongoose save()`
if (result === someCondition) {
user.meta.likes += 1
user.markModified("meta.likes")
try {
await user.save()
return
} catch (error) {
console.log(error)
return
}
} else {
user.meta.likes -= 1
user.markModified("meta.likes")
try {
await user.save()
return
} catch (error) {
console.log(error)
return
}
}
}
setInterval(getLikes, 2000)
There are some issues that need to be addressed in your code.
1) processUser is an asynchronous function. Array.prototype.forEach doesn't respect asynchronous functions as documented here on MDN.
2) setInterval doesn't respect the return value of your function as documented here on MDN, therefore passing a function that returns a promise (async/await) will not behave as intended.
3) setInterval shouldn't be used with functions that could potentially take longer to run than your interval as documented here on MDN in the Usage Section near the bottom of the page.
Hitting an external api for every user every 2 seconds and reacting to the result is going to be problematic under the best of circumstances. I would start by asking myself if this is absolutely the only way to achieve my overall goal.
If it is the only way, you'll probably want to implement your solution using the recursive setTimeout() mentioned at the link in #2 above, or perhaps using an async version of setInterval() there's one on npm here

Using Node.Js and Promises, how do you return a fulfilled promise? [duplicate]

This question already has answers here:
MongoDB atomic "findOrCreate": findOne, insert if nonexistent, but do not update
(4 answers)
Mongoose - Create document if not exists, otherwise, update- return document in either case
(5 answers)
Closed 4 years ago.
I am using Mongoose and am in a situation in which I am looking up a customer from the database. If the customer exists, I will return a customerId. However, if the customer does not exist, I want to create them and then return the customer id. In either case, I want the promise to end with a customerID. Anyway, while I have seen in the documentation that is possible to instantly wrap a return value using "Promise.resolve", I am receiving an error. This feature is apparently useful when trying to make synchronous tasks more congruent with Node by wrapping their return values. Anyway, what is a better way to deal with this or return a promise? At the end of the day I want the code to return the customer object or fall through to a catch/error.
TypeError: Promise.resolve is not a constructor
Below is the code (I edited out some security things, but I think I didn't make any serious alterations).
Customer.findOne({email: email}).then((customer) => {
if (customer) {
return Promise.resolve(customer);
}
else {
return new Customer({email: email}).save();
}
}).then( function(customer) {...
This is really about Promises, not Mongoose or MongoDb. If you are in a situation in which you are inside of a promise and want to return a value to the chain, what do you do?
You don't need Promise.resolve() here. If a then handler returns a non-promise, the promise returned by the then call will resolve with that value. In other words, it will already be wrapped, so wrapping it again is redundant.
On an unrelated note, the else is also redundant, as return statements exit the currently executing function:
Customer.findOne({email: email}).then((customer) => {
if (customer) {
return customer;
}
return new Customer({email: email}).save();
}).then( function(customer) {...
Promise.resolve() and Promise.reject() are generally intended for operations that have special cases where no actual asynchronous operation takes place, but you'd still like them to return promises in those cases for consistency's sake.
As a trivial example, imagine I have a bunch of objects with uid properties that may or may not have a corresponding username property. I want to make sure all of them have a username property, so I write a function I can call on all of them, which will look up the username from a database if it is missing. For consistency I want this function to always return a promise, even if the username is already there and no lookup is actually necessary. Also, I can add a case where, in the event that the uid is missing, the promise will reject without wasting time trying to look up undefined as a uid.
Could look something like this:
function ensureUsername(user) {
if ('username' in user) return Promise.resolve(user);
return lookUpUsernameByUid(user.uid)
.then((username) => {
user.username = username;
return user;
});
}
Promise.resolve() is also quite useful when dealing with values that may or may not be promises. This is because it simply returns the value if it already is a promise.
Let's say you want to write a function foo that invokes some callback function. The callback function may or may not be asynchronous-- i.e. it may return a promise, but is not required to. How do you accomplish this?
It's simple. Just put the result of the callback in Promise.resolve and chain a then onto it:
function foo(cb) {
return Promise.resolve(cb())
.then((callbackResult) => {
// Do whatever...
});
}
This will work whether cb returns a promise that resolves with callbackResult or simply returns callbackResult directly.

Passing a callback to a function that is prefixed with await. Order of excecution

Let's say we have a mongoDB with cars, and users can add cars. The goal is to prevent more than one car with an identical license plate to be saved. Stack is node/express and mongoose.
Scenario: A car with the provided license plate DOES already exist in the db.
First attempt:
console.log('Before check')
await Car.findOne({ licensePlate }, (err, foundCar) => {
if (err) return next(err)
if (foundCar) return res.status(400).send('license plate exists already in db.')
})
console.log('After check')
// some other code, saving to db and stuff
return res.status(200).send('The car was saved succesfully.')
My naive expectation was that if i prefix moongooses findOne function with an await keyword, the code after the "await block" will not be executed because a car is found and then the request is terminated by sending a 400 response. What happens instead is that the code after the "await block" is executed which results in a
Error: Can't set headers after they are sent.
Second attempt:
console.log('Before check')
try {
const foundCar = await Car.findOne({ licensePlate })
if (foundCar) return res.status(400).send('license plate exists already in db.')
} catch (err) {
return next(err)
}
console.log('After check')
// some other code, saving to db and stuff
return res.status(200).send('The car was saved succesfully.')
This works as expected and prevents any code from exceduting after the if(foundCar) check
Question: Can someone enlighten me what happens here in attempt one? Looking at it now it does feel weird to combine a callback with an await keyword, but apperently i don't understand async/await enough to really see what's happening here. Does await in this case mean that "the code waits" only for findOne to finish and then the callback and the rest of the code run at the same time? Any pointer to useful ressources to fully understand what's going on would be very much appreciated.
If the findOne function is not passed a callback function, it returns a Promise object.
If await is invoked with a Promise object, it will wait till the value is resolved by the promise and return the value.
If await is invoked with a non Promise object, it will convert that to a resolved promise.
In the first case, a callback is passed to the findOne, so it is not returning a Promise object and await converts that to a resolved promise. The value of that is resolved in the nextTick of the Node.js execution cycle. So there is no waiting for the results is happening here and even before the callback function is invoked response 200 is sent. When the callback is actually executed, it is trying to send the response headers again and since they are already sent, you are getting this error.
In the second case, findOne actually returns a Promise object. So await waits till the Promise resolves to a value. That is why the second case is working properly.

How to ensure that the function doesn't return until mongodb query is complete [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Waiting for async call
(1 answer)
Closed 8 years ago.
I have a User model function all that returns all users in an array.
User = {
all: function() {
var users = [];
globalLibrary.db.collection('users').find({}).toArray(function(err, items) {
test.equal(null, err);
users = items;
});
return users;
}
}
I want to ensure that the function doesn't finish or return before the mongodb query is complete.
Currently, this function is just returning [], and querying mongodb asynchronously. I want the function to wait until the function finish query is complete and it returns and array filled with users.
Note:
globalLibrary.db is just a cached mongodb connection.
My solution using promises
Since some people closed the question as a duplicate, I'll write my answer here within the question. Hopefully someone else who is not familiar with asynchronous programming find this useful.
Problem
The point is that you need to use a callback - there's no way to block
on something asynchronous. (...) – Aaron Dufour
The function User.all() I wrote above will return empty array because nothing is stopping the process while the mongodb query is happening. There are some primitive ways to stop the process.
You can crank up some hacky stuff using setTimeout(). This way sucks though because you have to use some arbitrary time that might be higher than the actual time you need to query the mongodb. Simply put, it's slower.
You can also use some event based stuff that #AaronDufour linked in the comment (now deleted). So you can have something a pair of event emitter and a listener to replace setTimeout() way. #Someone points out though that you shouldn't use this in node.js for blocking function.
Now finally, the conventional way of dealing with this problem is using callbacks as pointed out by the answer below. This is fine, but callbacks can quickly get out of control once you start having multiple callbacks stacked inside one another.
I am using https://github.com/kriskowal/q for promises. While using promises doesn't solve all the woes of callbacks, but it looks most like simple synchronous programming style which I think is a huge plus.
First do npm install --save q to start using Q package.
Here's my new User.all() function.
var Q = require('q')
var Users = {
all: function() {
var deferred = Q.defer();
globalLibrary.db.collection('users').find({}).toArray(function(err, items) {
if (err) {
deferred.reject(new Error(err));
} else {
deferred.resolve(items);
}
});
return deferred.promise;
}
}
Now if you want to use User.all().
User.all()
.then(function (docs) {
// docs is the array of returned users from mongodb query.
console.log(docs);
}, function(error) {
// do something if there's an error.
}, function(progress) {
// do something while the query is running.
});
The preferred way of doing this in node.js is to embrace the async nature of it and pass in a callback to the function. A few decent tutorials (last one including mongodb examples):
http://justinklemm.com/node-js-async-tutorial/
http://msdn.microsoft.com/en-us/magazine/dn754378.aspx
If you feel you must go against the grain and go synchronous, I'd take a look at this sync library for node.js and mongodb:
https://www.npmjs.com/package/mongo-sync

Resources