-1

I have a route to comments where i want to fetch all comments for a particular docket no. Again for every comment there may be multiple files. i Want to fetch them all in a synchronous way, whereas with nodeJS by default its work in a asynchronous way. Both comments and files are stored in different collections.

This is the route where I want to return the result as a single JS object

router.get("/history/:docket_no", (req, res) => {
    Comments.find({ docket_no: req.params.docket_no })
    .exec()
    .then(comments => {
        var cmnts = [];
        if (comments.length >= 1) {
            for(var i = 0; i < comments.length; i++) {
                var cmntImgs = [];
                CommentImage.find({ comment_id: comments[i]._id })
                .exec()
                .then(commentImages => {
                    for(var j = 0; j < commentImages.length; j++) {
                        var filePath = commentImages[j].comment_image;
                        var fileName = filePath.split("\\");
                        cmntImgs[j] = {
                            filesName: fileName[fileName.length-1],
                            filePath: filePath
                        };
                    }
                    console.log('Images',cmntImgs);
                })
                .catch(err => {
                    console.error(err);
                });

                cmnts[i] = {
                    id: comments[i]._id,
                    user_name: comments[i].user_name,
                    role_id: comments[i].role_id,
                    message: comments[i].message,
                    create_date: comments[i].create_date,
                    files: cmntImgs
                };
            };

            return res.status(200).json({
                comments: cmnts
            });

        } else {
            return res.status(400).json({
                msg: "No comments found"
            });
        }
    })
    .catch(err => {
        console.error(err);
        res.send(500).json({
          msg: "Error"
        });
    });
});

This is the Comment Schema...

const mongoose = require("mongoose");

//Comments Schema
const CommentsSchema = mongoose.Schema({
    docket_no: {
        type: String,
        trim: true,
        required: true
    },
    user_name: {
        type: String,
        required: true
    },
    role_id: {
        type: Number,
        required: true
    },
    message: {
        type: String,
        trim: true,
        required: true
    },
    create_date: {
        type: Date,
        default: Date.now,
        required: true
    }
});

module.exports = mongoose.model("Comments", CommentsSchema);

This is the Files Schema...

const mongoose = require("mongoose");

//CommentImage Schema
const CommentImage = mongoose.Schema({
    docket_no: {
        type: String,
        trim: true,
        required: true
    },
    comment_id: {
        type: String,
        trim: true,
        required: true
    },
    comment_image: {
        type: String,
        trim: true,
        required: true
    }
});

module.exports = mongoose.model("CommentImage", CommentImage);

This is the current output-

{
    "comments": [
        {
            "id": "5c3efdf77ad5e5176cc65bc4",
            "user_name": "aaa",
            "role_id": 1,
            "message": "comment test 1",
            "create_date": "2019-01-16T09:48:39.023Z",
            "files": []
        },
        {
            "id": "5c40311bdad6ad1084d24b9c",
            "user_name": "aaa",
            "role_id": 1,
            "message": "comment test 1",
            "create_date": "2019-01-17T07:39:07.081Z",
            "files": []
        }
    ]
}

Desired Output-

{
    "comments": [
        {
            "id": "5c3efdf77ad5e5176cc65bc4",
            "user_name": "aaa",
            "role_id": 1,
            "message": "comment test 1",
            "create_date": "2019-01-16T09:48:39.023Z",
            "files": []
        },
        {
            "id": "5c40311bdad6ad1084d24b9c",
            "user_name": "aaa",
            "role_id": 1,
            "message": "comment test 1",
            "create_date": "2019-01-17T07:39:07.081Z",
            "files": [
                        {
                            filesName: '1547710746976-FD Direct Screen #2.jpg',
                            filePath: 'uploads\\1547710746976-FD Direct Screen #2.jpg' },
                        {
                            filesName: '1547710746987-New Doc 2018-05-31.doc',
                            filePath: 'uploads\\1547710746987-New Doc 2018-05-31.doc' },
                        {
                            filesName: '1547710747024-New Doc 2018-05-31.docx',
                            filePath: 'uploads\\1547710747024-New Doc 2018-05-31.docx' },
                        {
                            filesName: '1547710747053-New Doc 2018-05-31.pdf',
                            filePath: 'uploads\\1547710747053-New Doc 2018-05-31.pdf' }
                    ]
        }
    ]
}
Xvistasss
  • 66
  • 9
  • 1
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Liam Jan 18 '19 at 12:19
  • also see [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Liam Jan 18 '19 at 12:21

1 Answers1

0

Mongoose does not support synchronous operations. You will need to turn your route handler into an async function in order to take advantage of await. This makes it seem/read like it's synchronous.

Error handling should be delegated to Express' error middleware.

router.get("/history/:docket_no", async (req, res) => {
    const { docket_no } = req.params
    const comments = await Comments.find({ docket_no }).exec()

    if (comments.length < 1) {
        res.status(400).json({ msg: "No comments found."})
        return
    }

    const mappedComments = []
    const mappedImages = []

    for(let i = 0; i < comments.length; i++) {
        const images = await CommentImages
            .find({ comment_id: comments[i]._id })
            .exec()

        for(let j = 0; j < images.length; j++) {
            const filePath = images[j].comment_image
            const fileName = filePath.split("\\")
            mappedImages[j] = {
                filesName: fileName[fileName.length - 1],
                filePath
            }
        }

        console.log(`Images ${mappedImages}`)

        const {
            _id,
            user_name,
            role_id,
            message,
            create_date
        } = comments[i]

        mappedComments[i] = {
            id: _id,
            user_name,
            role_id,
            message,
            create_date,
            files: mappedImages
        }
    }

    res.json({ comments: mappedComments })
})

The above example also takes advantage of shorthand property names and object destructuring

Cisco
  • 20,972
  • 5
  • 38
  • 60
  • 2
    This serializes the database access (i.e. it's slower than it needs to be). async/await is nice, but it's not a good thing to use in a loop. – Tomalak Jan 18 '19 at 12:33
  • Thanks this what I was looking for. This thing worked for me. But you removed the then and catch block completely. Is there any way to do it in the then and catch block. – Xvistasss Jan 20 '19 at 07:13
  • Yes the response is slow in my case as there is 2 loops working. – Xvistasss Jan 20 '19 at 07:15
  • @Tomalak there's no difference in performance between promises and async/await with the same implementation – Szymon D Jan 20 '19 at 08:53
  • There is, when you do `async`/`await` in a `for` loop. – Tomalak Jan 20 '19 at 09:39
  • @Xvistasss No, `catch and `then` are functions a `Promise` object. You asked for a _synchronous_ way which using `async-await` gives you that "synchronous" feel you were after. – Cisco Jan 21 '19 at 00:32
  • Thanks buddy @FranciscoMateo – Xvistasss Jan 21 '19 at 07:01