Help with Understanding Callback in Node/Express App

Hey! I need some help understanding what’s going on here…

I have been taking this Mongo DB course and the final project is making this website. My confusion is how this callback situation is working. For the first task, I am supposed to make a function that query’s all the categories from the items in a database (for a store) and make an array of the item categories and the totals. I have figured out who to actually create the function using some help online, but I really don’t understand what’s happening with the final callback(result). I’m lost as to how that relates to the initial this.getCategories = function (callback).

Here are some parts of the app that might be helpful. I feel like I have usually understood callbacks, but feel jumping into the “deep end” with this node/express app (I’m pretty new to that) is revealing things about callbacks that I don’t totally understand…

*Side note: most of this app was pre-written for the assignment, and I am just trying to fill in gaps, hence things like the entire MongoClient below that’s completed for me already.

This is part of me items.js file that has the function in question I’m not really understanding.

var MongoClient = require('mongodb').MongoClient,
    assert = require('assert');


function ItemDAO(database) {
    "use strict";

    this.db = database;

    this.getCategories = function (callback) {
        "use strict";

        var categoryArray = this.db.collection("item").aggregate([{
                $match: {
                    "category": {
                        $exists: true
                    }
                }
            },

            {
                $group: {
                    _id: "$category",
                    num: {
                        $sum: 1
                    }
                }
            },
            {
                $sort: {
                    "_id.category": 1
                }
            }
        ]).toArray(function makeArray(err, result) {
            var allItems = 0;
            for (var i = 0; i < result.length; i++) {
                allItems += result[i].num;
            };
            result.unshift({
                _id: "All",
                num: allItems
            })
            console.log(result);
            callback(result);
        });
}

This is the mongomart.js file (the part that establoishes the connection) that has the homepage where the function is getting used, in case that helps at all.

MongoClient.connect('mongodb://localhost:27017/mongomart', function (err, db) {
    "use strict";

    assert.equal(null, err);
    console.log("Successfully connected to MongoDB.");

    var items = new ItemDAO(db);
    var cart = new CartDAO(db);

    var router = express.Router();

    // Homepage
    router.get("/", function (req, res) {
        "use strict";

        var page = req.query.page ? parseInt(req.query.page) : 0;
        var category = req.query.category ? req.query.category : "All";

        items.getCategories(function (categories) {

            items.getItems(category, page, ITEMS_PER_PAGE, function (pageItems) {

                items.getNumItems(category, function (itemCount) {

                    var numPages = 0;
                    if (itemCount > ITEMS_PER_PAGE) {
                        numPages = Math.ceil(itemCount / ITEMS_PER_PAGE);
                    }

                    res.render('home', {
                        category_param: category,
                        categories: categories,
                        useRangeBasedPagination: false,
                        itemCount: itemCount,
                        pages: numPages,
                        page: page,
                        items: pageItems
                    });

                });
            });
        });
    });

I’m sorry, I don’t have time to go through your code, but I think it sounds like you are confused about sending callbacks.

Yes, it is a confusing subject. Remember that in JS, functions are first class citizens are are can be passed around as needed. This is a common pattern where you send your own callback function. We are used to sending callback functions, but the first time you write a function that receives a callback function, it is a strange thing. We are simply sending it a function to do something with whatever data we get.

function getLocation(callback) {
  fetch('https://ipapi.co/8.8.8.8/json/', { method: 'GET' })
  .then((response) => response.json())
  .then(location => {
    callback(location.latitude, location.longitude)
  })
}

//***

function showCoordsEng(lat, lon) {
  console.log('Your location is:', lat, lon)
}

function showCoordsEsp(lat, lon) {
  console.log('Tu lugar es:', lat, lon)
}

//***

getLocation(showCoordsEng)

getLocation(showCoordsEsp)

getLocation((lat, lon) => {
  console.log('Ta position est:', lat, lon)
})

Here, we create a function to get the user location and pass in a callback function to do something with that. The first two times we pass in predefined functions, and the third time we send in an anonymous function defined on the spot.

Again, sending in the callback function is something we see all the time. The new concept writing a function that accepts the callback function and does something with it. That has been mostly hidden from us. But it is a very useful pattern for asynchronous actions. It caught me off guard when I started doing backend stuff and had to do this. The JS let’s you pass around functions is part of its magic.

You can see the pen here.

I actually just finished the same course myself and had similar confusion about how the callbacks were working. Once you figure out one of the labs, the rest become much easier (assuming you’ve paid attention in the other weeks of the course).

The callback is passed in whenever some other part of the code (in this case, mongomart.js) calls the function. For the purposes of the exam, you can basically treat it as a black box — a “do something I don’t care about” with the returned value. All you need to worry about is that that returned value itself is the correct one, in this case, an array of categories.

In fact, for toArray, you should be able to get away with just (err, val) => callback(val) (plus error handling if you wish). For getCategories, I kinda cheated by unshifting the “All” category onto that array before passing it to the callback, rather than doing that stage in Mongo, but apart from that, your logic should all reside in the aggregation pipeline.