Why does mongoose run asynchronously when creating and updating a document? - javascript

In the following function, after creating the work document, I save the images from the request retrieved through multer module.When saving the image documents I try to update the work document by pushing all the _ids of the images.
But somehow, if you take a look at the code bellow and focus on the console.logs, the second console.log is being executed first, although I used .then when creating the images.That also means that I get an outdated work document on the final lines of code.
The docs say that Model.create() returns a Promise, which means it should run synchronously if I use .then() (if I'm not mistaken). But this is not the case in my function:
function addToDB(req, res, model) {
let imagesToAdd = [];
Works.create(model)
.then(work => {
req.files.forEach(image => {
let path = image.path.split('').map(char => char === '\\' ? '/' : char).join('');
let imageObj = {
fileName: image.filename,
mimeType: image.mimetype,
imageURL: `${req.baseURL}/${path}`,
workId: work._id
};
imagesToAdd.push(imageObj);
});
Images.create(imagesToAdd)
.then(createdImages => {
let imageIds = [];
createdImages.forEach(image => {
console.log(image._id);
imageIds.push(image._id);
});
Works.updateMany({_id: work._id}, {$push: {images: {$each: imageIds}}}).catch(err => handleError(err));
})
.catch(err => handleError(err));
console.log('>'+work._id);
return work._id;
})
.then(workId => {
Works.findById(workId)
.then(foundWork => {
res.json(foundWork);
})
.catch(err => handleError(err));
})
.catch(err => handleError(err));
}
And here is the console after POSTing a work document:
cmd after execution:
And there is the response:
response after execution:
There 2 images were added.Above you saw in the response that images array doesn't have any element, while in mongo the image ids were saved:
The saved work after execution:
The end goal is to respond with the newly created work, which has the image ids included, so I can further populate the images array of the work document and workId in the image document.
How can I make the code run synchronously ?

The problem is that this:
Images.create(imagesToAdd)
.then(createdImages => {
let imageIds = [];
createdImages.forEach(image => {
console.log(image._id);
imageIds.push(image._id);
});
Works.updateMany({_id: work._id}, {$push: {images: {$each: imageIds}}}).catch(err => handleError(err));
})
is set to run asynchronously, then this:
console.log('>'+work._id);
return work._id;
is executed, you go to the next then, and after some time the result of the first promise is returned.
The correct way would be:
function addToDB(req, res, model) {
let imagesToAdd = [];
Works.create(model)
.then(work => {
// Populate imagesToAdd
req.files.forEach(image => {
let path = image.path.split('').map(char => char === '\\' ? '/' : char).join('');
let imageObj = {
fileName: image.filename,
mimeType: image.mimetype,
imageURL: `${req.baseURL}/${path}`,
workId: work._id
};
imagesToAdd.push(imageObj);
});
Images.create(imagesToAdd)
.then(createdImages => {
// images have been created
let imageIds = [];
createdImages.forEach(image => {
console.log(image._id);
imageIds.push(image._id);
});
// imageIds has been populated
Works.updateMany({_id: work._id}, {$push: {images: {$each: imageIds}}})
.then(() => {
// After update is finished, findById
Works.findById( work._id )
.then( foundWork => {
// Return the work after it has been updated with images
res.json( foundWork );
} )
.catch( err => handleError( err ) );
})
.catch(err => handleError(err));
})
.catch(err => handleError(err));
})
.catch(err => handleError(err)); }
Even simpler way is to use async / await.

Related

What method is used to modified a property from an array of records returned from a Mongoose query?

const images = await tbl
.find({
creator_id: req.user._id,
})
.select({
creator_id: 0,
})
.then((images) =>
images.forEach((image) => {
image.file_name = process.env.IMAGE_HOST_URL + image.file_name;
})
);
The code above fails at the .then bit.
What is the proper mongoose method to replace it with to achieve what is intended in the code?
What do you mean fails?
The find returns two objects. An err and res.
the .then should be
.then((err, res) => {
if (err) {...}
else {
res.forEach(img => {
image.file_name = process.env.IMAGE_HOST_URL + image.file_name;
img.save()
})
}
})
You will get a list of documents that match the filter of the find as the res (error object, which should be null, is first). And you must save it after the changes.

Instead of getting undefined, how can I get the data from the asynchronous call?

subject in user.subjects = subjects below is undefined instead of being an array of subjects.
Background:
I have three tables - users, subjects, and a relation between users and subjects called users_subjects.
My goal is that the final res should include an array of subject names.
To do this, I need three asynchronous calls.
Get the users from the users table
Get the subjects is for each user from the table representing the relation between users and subjects (users_subjects)
Get the name for each subject id from subject table
My challenge is getting inputing the subject id and receiving the name. the upper half of addSubjects is my attempt to solve it, albeit unsuccessfully:
//for every user take its id
//run its id with tutors-subjects to get all subject id with that user id
//for each subject id get is subject name through with subjects service
//add subjects key
//add each subject to an array
const addSubject = (knexInstance, users, res) => {
let resultsName = []
function testIt(subjectsArray) {
// let resultsName = []
Promise.all(
subjectsArray.map((id) =>
SubjectsServes.getById(knexInstance, id)
.then(subjectNameObject => {
console.log(subjectNameObject)
resultsName.push(subjectNameObject.subject_name)
})
.catch(err => {
//console.log(err)
})
)).then(() => {
return resultsName
})
}
let results = []
Promise.all(
users.map((user) =>
TutorsSubjectsService.getAllSubjectsForATutor(knexInstance, user.user_id)
.then(subjectsIdArray => {
return testIt(subjectsIdArray)
})
.then(sub => {
user.subjects = sub
results.push(user)
})
.catch(err => {
//console.log(err)
})
)).then(() => {
return res.json(results)
})
.catch(err => {
//console.log(err)
})
}
The following code is where it starts - it calls the above function:
.get((req, res, next) => {
const knexInstance = req.app.get('db');
UsersService.getAllUsers(knexInstance)
.then(Users => {
return addSubject(knexInstance, Users, res)
})
.catch(next)
The only problem I have is with the testIt function. Everything till that point works fine. They're just here for context.
My question is how can I get the correct subjectNames back from testIt?
(The return in testIt last then does not help; the whole function testIt just returns undefined as said in the title)
const testIt = (subjectsArray)=> {
return subjectsArray.map((id) => {
return SubjectsServes.getById(knexInstance, id);
}).reduce((acc,el)=>{
acc.push(el);
return acc;
},[])
//return array of promises
}
const addSubject = (knexInstance, users, res) => {
Promise.all(
users.reduce((acc,el)=>{
acc.push(TutorsSubjectsService.getAllSubjectsForATutor(knexInstance, el.user_id)); return acc;
},[])
).then((userResults) => {
//resolve all promises userResult is an array of result from promises above
Promise.all( userResults.reduce((acc,el)=>{
//for each user create array of promises given the subject list
acc = acc.concat(testIt(el))
} ,[]).then((results)=>{
return res.status(200).send(result);
}).catch( (err)=>{
console.log(err);
return res.status(500).end();
} )
})
.catch(err => {
console.log(err);
return res.status(500).end();
})
}
when you .then() it means you are resolving promises. So nothing return to promise.all. Promise.all().then( result=>{} )
result is an array of output from promises. You have to chain all promises inside each other. They are async, 2 promises all run concurrently. So your result name will be ambiguous, possibly inital value (empty array)

Javascript how to chain multiple promises

In parse I have this crazy query where I'm querying on table1 to get an object.
Then querying table2 on a pointer column that matches table1's result for all users that match that.
Next I need to create an object and then create another object using the result of that first object.
Finally save the final object into the users from table2's query.
I'm having an issue chaining everything and for some reason my success message returns before the user objects are saved.
Parse.Cloud.define('startChain', (req, res) => {
let q1 = new Parse.Query("Table1");
q1.equalTo("objectId", req.params.q1ID);
q1.equalTo("user", req.user);
q1.include("user");
q1.get(req.params.q1ID)
.then(post => {
post.get("user")
.then(user => {
// Query on q1
let q2 = new Parse.Query("Table2");
q2.equalTo("t1Object", post);
w2.include("user2");
q2.include("pointer2Object");
q2.find();
})
.then(users => {
var morePromises = users.map(aUser => {
let newObject = new Parse.Object.Extend("Table3");
newObject.set("user", aUser);
newObject.set("table1Pointer", post);
newObject.save()
.then(result => {
var object2 = new Parse.Object.Extend("Table4");
object2.set("column1", aUser);
object2.set("column2", result);
var object3 = new Parse.Object.Extend("Table5");
object2.save()
.then(o2 => {
object3.set('column', 'o2');
object3.save()
.then(o3 => {
aUser.set("o3", o3);
return aUser.save(null, {useMasterKey: true});
});
});
});
});
Promise.all(morePromises)
.then(result => res.success());
})
.catch(err => {
res.error(err.message);
});
});
});
In the first lines
q1.get(req.params.q1ID)
.then(post => {
the argument to then()'s callback is whatever is returned by q1.get().
Following the same logic, you can chain promises on a single level (i.e. not nested) by returning from a chain block what you need in the next. E.g the above could continue like
q1.get(req.params.q1ID)
.then(post => {
...
return q2.find();
}).then( users => {
// users is available here
...
return object2.save();
}).then( o2 => {
});
And so forth...
Ideally you should use async await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
would be much cleaner, code example below:
async function yourFunction(x) {
const result = await q2.find();
result.map((newObject) => {
await newObject.save();
});
}
not sure your browser support tho.

How do I access previous promise response in a .then() chain in axios?

I need access to responseA to get access to a field value further along the chained request. How can that be done elegantly?
axios.get(`/endpoint`)
.then((responseA) => {
// Do something with responseA
return axios.put(signedUrl, file, options);
})
.then((responseB) => {
// Do something with responseA & responseB
})
.catch((err) => {
console.log(err.message);
});
UPDATE: I should probably mention that in my first .then() I return another network request. And it has to happen in sequence.
What is happening inside the first .then() block? In theory, you could return the values you need from responseA to the second .then() block, as whatever you return from the first then block will be available as responseB. In other words, it would look something like this:
axios.get(`/endpoint`)
.then((responseA) => {
// additional request/logic
const moreData = someFunction()
return { responseAdata: responseA.dataYouWant, moreData: moreData }
})
.then((responseB) => {
// now responseA values will be available here as well, e.g.
responseB.responseAdata
// Do something with responseA & responseB
})
.catch((err) => {
console.log(err.message);
});
You have a number of options to do this.
1) Break the chain
let promiseA = axios.get(`/endpoint`)
let promiseB = promiseA.then((responseA) => {
// Do something with responseA
})
return Promise.all([promiseA, promiseB]).then(function([responseA, responseB]) {
// Do what you must
});
2) Use await
let responseA = await axios.get('/endpoint/')
// You can figure out the rest
You can use Promise.all:
axios.get(`/endpoint`)
.then(
responseA =>
Promise.all([
responseA,
axios.get("/endpointB")
])
)
.then(
([responseA,responseB]) => {
console.log(responseA,responseB);
})
.catch((err) => {
console.log(err.message);
});
If anyone is still facing problem then try as below :
axios.get('https://api.openweathermap.org/geo/1.0/direct?q=' + req.body.city + '&limit=1&appid=e43ace140d2d7bd6108f3458e8f5c')
.then(
(response1) => {
let output = response1.data;
axios.get('https://api.openweathermap.org/data/2.5/weather?lat=' + output[0].lat + '&lon=' + output[0].lon + '&appid=e43ace1d1640d2d7bd61058e8f5c')
.then((weatherdd) => {
res.render('index.twig', { weatherdata: weatherdd.data, cityname: req.body.city, todaydatex: todayDate });
})
}
)
.catch(
err => {
res.send(err)
}
);
Hint: As You can see I am using response1 which is returned from the first request and then defined a local variable with output and finally using the data in my next HTTP request (eg: output[0].lat)

Issue with async promise

i'm trying to get a list of matching entities from a querystring. i'm using s3 to store my objects.
The problem is that the promise that resolves the 'main promise' is ansych only return the array when given a setTimeOut. Else it returns [undefined, undefined...].
my code looks like this:
getEntities: (type, offset, limit, query) => {
return new Promise((resolve, reject) => {
seekKeys(type, offset, limit, query)
.then((response) => {
let entities = [];
if (response.hasBeenFiltered) { // This is not used yet.
for (let i = offset; i < response.keys.length && i < offset + limit; i++) {
entities.push(matchInstance(instance))
}
resolve(entities)
} else { // here is where my issue's at.
console.log("Keys found: " + response.keys.length)
parseQuery(type, query)
.then((conditions) => {
let matches = response.keys.map((key) => {
readEntity(key).then((instance) => {
logger.debug(instance); // logs instances.
matchInstance(instance, conditions)
.then((isMatch) => {
logger.debug("isMatch", isMatch) // logs true/false ?
if (isMatch) {
entities.push(instance);
}
})
.catch((err) => {
logger.error("Failed to match entity: ", err)
})
})
.catch((err) => {
logger.error("Failed to read entity: ", err)
})
});
/*
Promise.resolve(matches)
.then(() => {
setTimeout(() => {
logger.debug("Match count:", entities.length);
logger.debug("Matching entities:", entities) // logs result of entities
}, 5000)
//resolve(entities)
})
*/
Promise.resolve(matches)
.then(() => {
logger.debug("Match count:", entities.length);
logger.debug("Matching entities:", entities) // logs [undefined, undefined ....]
resolve(entities)
})
})
.catch((err) => {
console.error("Failed to parse query: ", err)
});
})
})
}`
The format is a bit broken. I'm quite sure why.
Please let me know if you need more info.
let matches = response.keys.map((key) => {
// the mapping function:
readEntity(key).then((instance) => {
The mapping function does not appear to return a value. This will create a mapped array with undefined elements.
let matches = response.keys.map((key) => {
return readEntity(key).then((instance) => {
// mapping function
may work better by filling matches with promises to read and process entities. Waiting for the promises to be fulfilled can be accomplished with Promise.all, so
Promise.all(matches).then( ... process the entities array
is more likely to work than
Promise.resolve(matches).then( ... process entities
which won't wait for anything asynchronous unless matches is a pending promise - but if that were so, you would not need to resolve it before calling then.
Note the code indentation is confusing, so check the console for errors to see if matches is in scope when processed - I was unable to see that it was.

Categories

Resources