mongoose promise and callback function input - javascript

I have went through some basic work on callback function and promise. But when I see the following code, I really can't find where did that zzz term come from.
I thought (zzz) is gonna be a input to callback function. inside the callback function, zzz was never defined.
return Dishes.find({}).exec() is only execution which returns nothing. even it returns something, it will go to output of the callback function instead of input, which is (zzz).
sorry if the question is stupid, i just looked at this point for 2 hours and can't figure out... thanks
const mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
const Dishes = require('./models/dishes');
const url = 'mongodb://localhost:27017/conFusion';
const connect = mongoose.connect(url, {
useMongoClient: true
});
connect.then((db) => {
console.log('Connected correctly to server');
var newDish = Dishes({
name: 'Uthappizza',
description: 'test'
});
newDish.save()
.then((zzz) => {
// ??? where did this zzz come from ??????????????????????????????????????????????
console.log(zzz);
return Dishes.find({}).exec();
})
.then((xxx) => {
console.log(xxx);
return db.collection('dishes').drop();
})
.then(() => {
return db.close();
})
.catch((err) => {
console.log(err);
});
});

Since the save() method is asynchronous it returns a Promise, and chaining it to the then() method you entered the callback function and passed it the ‘zzz’ argument.
‘zzz’ is the newDish object that you saved.
You can use the find({}).exec() in any case to find all objects in the database and execute a callback with the results.

Related

Async functions executing out of order

I am trying to execute the following code, however "// go do something else" keeps happening before "// do stuff with things"
It appears that my code is not waiting for mongoose.model('things').find() to finish before moving on. I've tried different variations of async/await and nothing seems to work.
Not getting errors an everything executes, just out of order.
const asyncFunction = async () => {
mongoose.connect(`mongodb+srv://...`);
mongoose.model('things', {data:String});
mongoose.model('things').find((err, things)=>{
// do stuff with things
}
console.log('something');
}
const otherAsyncFunction = async () {
await asyncFunction();
// go do something else
}
otherAsyncFunction();
Your asyncFunction doesn't return anything, so there's no point awaiting it.
You have no Mongoose schema.
The syntax to create a Mongoose model (doc) is :
const thingsShema = new mongoose.Schema({ data: 'string' });
const Thing = mongoose.model('thing', thingsShema );
You are creating your model, but then your model isn't stored in a variable and you're not using it at all.
mongoose.model('things') : this line creates a new Mongoose model but you're not passing it any schema. Anyway you already did that on the previous line.
.find() is asynchronous, and you're not returning its value.
You aren't passing any argument to .find(), only a callback function.
This code should work better :
const asyncFunction = async () => {
await mongoose.connect(`mongodb+srv://...`);
const thingsSchema = new mongoose.Schema({ data: 'string' });
const Things = mongoose.model('things', thingsSchema);
const foundThings = await Things
.find({}) // Query conditions (here, querying for 'everything', an empty object)
.lean() // Returns simple JSON and not a collection of Mongoose objects
.exec(); // returns a true Promise and not just a thenable
console.log('foundThings = ', foundThings);
return foundThings; // this returns a Promise, because 'async' functions always do
}
const otherAsyncFunction = async () => {
const result = await asyncFunction();
console.log("Result = ", result); // Normally this will log the same thing as 'foundThings'
// go do something else
}
otherAsyncFunction();
async functions return Promise object. When you exec await asyncFunction(), 'await' wait status asyncFunction() - resolve or reject.But in your code there are no resolve() or reject() functions. So you need to do like this:
const asyncFunction = async (resolve) => {
mongoose.connect(`mongodb+srv://...`);
mongoose.model('things', {data:String});
mongoose.model('things').find((err, things)=>{
// do stuff with things
});
console.log('something');
resolve()
}
const otherAsyncFunction = async () => {
await asyncFunction();
// go do something else
}
otherAsyncFunction();

Cloud function not executing ref.once on realtime db

My cloud function was working fine this morning. But now it suddenly stopped working. So I added a few more logs and I noticed that rootRef.once was not executing. Why could this be happening? The function keeps on timing out.
exports.assignTeams = functions.https.onCall((data, context) => {
const judgeId = data.judgeId;
console.log("Assigning teams to judge ", judgeId);
var db = admin.database();
var rootRef = db.ref("/");
rootRef.once(
"value",
function(snapshot) {
console.log("Passed value ", snapshot);
processAssignedQueue(snapshot, judgeId);
},
function(error) {
console.log("ERROR:\n" + error);
return { teams: [] };
}
);
});
The console logs from Firebase:
The problem is that you're not returning a promise from the function that resolves with the data to send to the caller. Right now, your function is actually returning nothing, because once() is asynchronous and returns immediately before the query is complete. When a callable function returns without a promise, it shuts down immediately, an any asynchronous work will not complete.
Also, you should note that the return statements inside the callbacks you passed to once() are only return values from those individual function callbacks, not the entire function.
What you should do instead is make use of the promise returned by once(), and use that to determine what to return to the client. Here's a simple example:
return rootRef.once("value")
.then(snapshot => {
console.log("Passed value ", snapshot);
return { data: snapshot.val() }
})
.catch(error => {
console.log("ERROR:\n" + error);
return { teams: [] };
});
In order to write Cloud Functions effectively, you will need to fully understand how javascript promises work. If you don't work with them correctly, your functions will fail in mysterious ways.

Node throws UnhandledPromiseRejectionWarning for Mongoose requests using Promise.all with .catch() statement

I'm new to Node/Mongoose and am trying to handle errors correctly in a script to add players to a league. In the below code, explicitly thrown and non-Promise related errors are caught correctly by the .catch() statement, but rejected Promises are not.
For example, trying to pass an invalid userID throws User not found.
But if I test Promise rejection by disconnecting the database, I get the following:
(node:6252) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): MongoNetworkError: failed to connect to server [localhost:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]
Am I using Promise.all() and .catch() incorrectly somehow?
Just to be clear, I'm trying to work out why the error isn't being handled, not why the error is being thrown.
My script:
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
mongoose.connect(dbUrl, { useNewUrlParser: true });
const addUserToLeague = (userId, leagueId) => {
let foundUser = User.findById(userId);
let foundLeague = League.findById(leagueId);
return Promise.all([foundUser, foundLeague])
.then(arr => {
if(!arr[0]){
throw 'User not found';
}else if(!arr[1]){
throw 'League not found';
}
return arr;
})
.then(arr => {
arr[0].leagueMemberships.push(arr[1]);
arr[1].users.push(arr[0]);
return arr;
})
.then(updatedArr => {
updatedArr[0].save();
updatedArr[1].save();
return updatedArr;
})
.then(updatedArr => { console.log(`User ${updatedArr[0]._id} added to league ${updatedArr[1]._id}`) })
.catch(err => { console.log('Error:', err) });
};
addUserToLeague(process.argv[2], process.argv[3]); // Needs 2 args: User ID and League ID
As Bergi pointed out, the error would appear to be from connect, which returns a promise that you're not handling at all — including not waiting for it to finish. So at a minimum, you need to handle that:
const connectionPromise = mongoose.connect(dbUrl, { useNewUrlParser: true })
.catch(error => {
// Handle connection error
});
Then in addUserToLeague:
const addUserToLeague = (userId, leagueId) => {
return connectionPromise.then(connection => {
// ...logic here
});
};
...but, I question whether you should be connecting when the module is loaded like that, rather than passing a connection into addUserToLeague.
Aside from that, the actual use of Promise.all is okay, but:
One hopes that findById doesn't resolve the promise with a falsy value if the item isn't found, so that whole first then handler seems unnecessary.
Presumably save returns a promise. You're not handling rejection or waiting for resolution of those if so.
I'd use destructuring to avoid arr[0] and arr[1], as it's easy to forget the order.
There's no reason for the then handler with the push calls to be separate from the then handler doing the saving.
addUserToLeague should return the result of the promise chain, so that code calling it A) Knows when it's finished, and B) Knows when it fails.
Errors shouldn't be handled in addUserToLeague; instead, handle them in its caller.
There's also the issue that the data is denormalized: You're storing the membership information in both the user object and the league object. Maybe that's relatively normal in document databases (I wouldn't know); in an RDBMS you'd store the information in a single place. The reason is clear from the code in addUserToLeague: What if saving the user succeeds but saving the league fails? Then the user object says it's a member of a league the league object doesn't say it's a member of. There's also the problem that since it's stored in two places, even if nothing goes wrong, for a brief period one of the (the user or the league) will have been saved but the other won't have been. Both are integrity problems. If you can normalize it to storing this information in one place, that would be good. If you can't, you need to update the code so that it saves one of them, waits for that to succeed, saves the other, and if that fails attempts to undo the change to the first.
Something like this (I don't attempt to address the normalization issue here, that's a big picture thing):
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
const addUserToLeague = (connection, userId, leagueId) => {
return Promise.all([
User.findById(userId),
League.findById(leagueId)
])
.then(([user, league]) => {
user.leagueMemberships.push(league);
league.users.push(user);
return Promise.all([user.save(), league.save()]);
})
.then((([user, league]) => {
console.log(`User ${user._id} added to league ${league._id}`);
});
};
mongoose.connect(dbUrl, { useNewUrlParser: true })
.then(connection => addUserToLeague(connection, process.argv[2], process.argv[3]) // Needs 2 args: User ID and League ID
.catch(error => {
// Handle/report error
});
If you're using any recent verson of Node, you can use an async function:
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
const addUserToLeague = async (connection, userId, leagueId) => {
let [user, league] = await Promise.all([
User.findById(userId),
League.findById(leagueId)
]);
user.leagueMemberships.push(league);
league.users.push(user);
[user, league] = await Promise.all([user.save(), league.save()]);
console.log(`User ${user._id} added to league ${league._id}`);
};
mongoose.connect(dbUrl, { useNewUrlParser: true })
.then(connection => addUserToLeague(connection, process.argv[2], process.argv[3]) // Needs 2 args: User ID and League ID
.catch(error => {
// Handle/report error
});

How to return the result of a promise in a function

I'm really confused about why I can not return the JSON result from amazonMws.products.search() and could use some help understanding what is going on. When I write it this way gives me undefined:
function listMatchingProducts(query) {
const options = {
Version: VERSION,
Action: 'ListMatchingProducts',
MarketplaceId: MARKET_PLACE_ID,
SellerId: SELLER_ID,
Query: query
}
amazonMws.products.search(options, (err, res) => {
if(err){
throw(err)
return
}
return res
})
}
I also get undefined when using amazonMws.products.search().then().catch() as well.
If I return amazonMws.products.search() I get a promise back instead of the result.
Inside of the callbacks if I console.log(res) I get back the JSON result I'm expecting. So this led me to believe I need to use async await I think, but this results in Promise { <pending> }:
async function listMatchingProducts(query) {
const options = {
Version: VERSION,
Action: 'ListMatchingProducts',
MarketplaceId: MARKET_PLACE_ID,
SellerId: SELLER_ID,
Query: query
}
return await amazonMws.products.search(options)
.then(res => {
return res
})
.catch(e => errorHandler(e))
}
I am totally lost, so if someone could explain to me what is going on, that would be greatly appreciated.
The amazonMws.products.search function is asynchronous, meaning that it will give you a value later, and because of this, you can't get the value now. Instead, you'll have to say what you want to do later when you receive the value.
This is what returning the promise does. The promise itself is the representation of this value that you'll receive later.
function listMatchingProducts(query) {
const options = {
Version: VERSION,
Action: 'ListMatchingProducts',
MarketplaceId: MARKET_PLACE_ID,
SellerId: SELLER_ID,
Query: query
}
return amazonMws.products.search(options)
}
Then, when calling the function, attach a handler to the promise.
listMatchingProducts(someQuery)
.then(result => {/* do something with result */})
.catch(error => {/* handle the error */})
And, though you don't need to use async await here, it can make the code look a little nicer in some situations, as though it were synchronous. Here's what calling the above function would look like with async await:
async function getProducts() {
try {
const result = await listMatchingProducts(someQuery)
// do something with result
} catch (error) {
// handle the error
}
}
And, as usual, always consult the docs for any detail you're confused about:
Using promises
await keyword
function listMatchingProducts(query) {
const options = {
Version: VERSION,
Action: 'ListMatchingProducts',
MarketplaceId: MARKET_PLACE_ID,
SellerId: SELLER_ID,
Query: query
}
return amazonMws.products.search(options); //returns a promise
}
As others have pointed out, Promises just don’t work the way you think they do.
See my answer to function returning too early before filter and reduce finish for a (hopefully) clear explanation of the problem you face.

What kind of promise does the promisified MongoDB Node driver .forEach() return?

I have recently started using arrow functions, promises (bluebird) and MongoDB in my Node.js project. As you can see below, I promisified all the mongodb native driver. So far, works like a charm.
Now, I would like to use the .forEach() method, and I wonder how I should use it. Here is the idea :
var Promise = require("bluebird");
var mongodb = Promise.promisifyAll(require("mongodb"));
var MongoClient = mongodb.MongoClient;
MongoClient
.connect('the_URL_of_my_database')
.then( (database) => {
var db = database;
var myCollection = database.collection('my_collection');
myCollection
.find('my_awesome_selector')
.forEach( (document) => {
var selector = {hashkey: document.hashkey};
var update = {$set: {data: 'my_new_data'}};
myCollection
.updateOne(selector, update)
.then( (response) => {
// do something if needed
})
.catch( (err) => {/*update error handling*/});
})
.then( (/*do we get some kind of input here ?*/) => {
// do something once EVERY document has been updated
})
.catch( (err) => {/*forEach error handling*/});
})
.catch( (err) => {/*connection error handling*/});
My questions are :
What does the promisified .forEach() return ? Is it a promise that will be resolved once ALL documents have been updated ?
If my code doesn't work, any idea on how to implement it correctly ?
[Optional questions that would help me greatly :]
I have noticed the .each() method in the MongoDB driver. It is marked as "deprecated", but is there any use for it, or should I stick to .forEach() ?
Does the entire snippet make sense to you, or are there any rookie mistakes/improvements I should be aware of ?
Thanks a lot for any answer or advice !
[EDIT : I precised the update. I feel like I'm in callback hell all over again]
You don't need bluebird for any of this. The mongo driver has built in promises. Since forEach doesn't return anything, you can use map() to process each document update into a promise, use toArray() to reduce them into an array of promises, and then Promise.all() to resolve each promise within that array:
var mongodb = require("mongodb");
var MongoClient = mongodb.MongoClient;
MongoClient
.connect('the_URL_of_my_database')
.then((db) => {
var myCollection = db.collection('my_collection');
myCollection.find({})
.map((document) => {
var selector = { _id: document._id };
var update = { $set: { data: 'my_new_data' } };
return myCollection
.updateOne(selector, update)
.then((response) => {
// this is each resolved updateOne. You don't need
// this `.then` unless you want to post-process
// each individual resolved updateOne result
console.log(response.result);
// return each value to accumulate it in Promise.all.then()
return response.result;
});
})
.toArray() // accumulate the .map returned promises
.then((promises) => Promise.all(promises)) // iterate through and resolve each updateOne promise
.then((resolvedPromises) => {
// an array of all the results returned from each updateOne
console.log(resolvedPromises);
})
.catch((error) => {
// error handle the entire chain.
});
})
.catch((error) => {
// error handle the connection
});
I have noticed the .each() method in the MongoDB driver. It is marked as "deprecated", but is there any use for it, or should I stick to .forEach() ?
I assume .each() was deprecated in favor of .forEach() for consistency with the Array prototype.

Categories