Search code examples
javascriptmongoosees6-promisemultersynchronous

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


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:

enter image description here

And there is the response:

response after execution:

enter image description here

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:

enter image description here

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 ?


Solution

  • 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.