Model.find().cursor().eachAsync()
does not return the document values as expected.Instead they are wrapped inside an object containing the following keys: $__
, isNew
, errors
, _doc
.
Unless I am misunderstanding what eachAsync is supposed to be doing, I am assuming I should normally be able to do a simple .eachAsync(handler)
because the document shouldn't be wrapped the way it is.
v6.10.0
v4.9.1
return User.find(params.query, params.select, params.options)
.cursor()
.eachAsync(user => {
// HERE > user values will be wrapped in "user._doc"
// Cannot do "handler(user)", have to fallback on handler(user._doc)
return handler(user._doc).then(completeUser => users.push(completeUser));
})
.then(() => {
return Promise.resolve(users);
});
@JoeTheFkingFrypan that's probably just the mongoose document, but when you console log it should have the proper values on it like virtuals and the fields on the schema:
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const _ = require('lodash');
const co = require('co');
const GITHUB_ISSUE = `gh-5097`;
exec()
.then(() => {
console.log(`Program ran successfully`);
process.exit(0);
})
.catch(error => {
console.error(`Error: ${ error }\n${ error.stack }`);
process.exit(2);
});
function exec() {
return co(function*() {
const db = mongoose.createConnection(`mongodb://localhost:27017/${ GITHUB_ISSUE }`);
const schema = new mongoose.Schema({
name: String
});
schema.virtual('age').get(function() {
return 24;
});
const User = db.model('User', schema);
yield User.remove({});
for (let i = 0; i < 10; i++) {
yield User.create({ name: `Doc-${ i }` });
}
return User.find()
.cursor()
.eachAsync(function(user) {
console.log('user', user.name + ' ' + user.age);
})
.then(() => Promise.resolve())
});
}
Does this not log out:
user Doc-0 24
user Doc-1 24
user Doc-2 24
user Doc-3 24
user Doc-4 24
user Doc-5 24
user Doc-6 24
user Doc-7 24
user Doc-8 24
user Doc-9 24
for you?
Might be a relevant information : I'm using Bluebird v3.5.0
as promise engine.
The problem comes from the fact I needed to iterate over the result keys, which ended up iterating over $__
, isNew
, errors
, _doc
instead of the actual user object keys.
And yes, to answer your question, your snippet does work. Now, to better pinpoint the issue, your users need to have the following attribute : a nested JSON object.
const schema = new mongoose.Schema({
name: String
apps: {
example: {},
otherExample: {},
});
Then, add some content inside those nested items like
for (let i = 0; i < 10; i++) {
yield User.create({
name: `Doc-${ i }`,
apps: {
example: {
id: i,
date: new Date()
},
otherExample: {
id: i,
date: new Date()
}}
});
}
The issue I'm facing can be reproduced by modifing your snippet this way:
return User.find()
.cursor()
.eachAsync(user => {
// These two should work
console.log('user', user);
console.log('user', user.name + ' ' + user.age);
// This should trigger the issue with high verbosity
for(let example in user.apps) {
console.log('1', example);
//console.error('1', user.apps[example]);
}
// This should trigger the issue with less verbosity
for(let example of Object.keys(user.apps)) {
console.log('2', example);
//console.error('2', user.apps[example]);
}
// This WORKS AS INTENDED (by using 'user._doc' instead of 'user')
for(let example in user._doc.apps) {
console.log('3', example);
//console.error('3', user.apps[example]);
}
// This ALSO WORKS AS INTENDED (by using 'user._doc' instead of 'user')
for(let example of Object.keys(user._doc.apps)) {
console.log('4', example);
//console.error('4', user.apps[example]);
}
})
.then(() => Promise.resolve())
eachAsync()
gives you a full fledged mongoose document. If you want the POJO version, you can either use .toObject()
or .lean()
return User.find()
.cursor()
.eachAsync(user => { user = user.toObject(); /* ... */ });
Or
return User.find()
.lean()
.cursor()
.eachAsync(user => { /** user is now a POJO */ });
Thanks for the tip!
Most helpful comment
eachAsync()
gives you a full fledged mongoose document. If you want the POJO version, you can either use.toObject()
or.lean()
Or