0

Ok,

I'm pretty new to node.js and the whole concept of callback functions, and I've been racking my brain trying to figure this out. Maybe some of you guys/gals can help.

So the basic setup is that I have a React/Redux app on the front-end that uses an API as a data source. On the front, I want to load some number of products at once and load all of their images, then I have a Product component that shows a single product, and would show an image gallery (which is why I need all the product images at once)

I'm trying to write an API the when I request http://myapi.com/randomproducts/ which will return an indicated number of random products from a MySQL database. That part I have. Here's the part that is throwing me... each product that I return in this array of products has an unknown of images associated with it. I want to load those at the same time such that my what I return looks something like this:

[
  {
    product_id: 0,
    product_name: "my cool product",
    product_desc: "desc of my cool product",
    price: 10.00,
    product_images: [
          {
            product_image_id: 10,
            product_image:  "img1.jpg",
          },
          {
            product_image_id: 11,
            product_image:  "img2.jpg",
          },
          {
            product_image_id: 12,
            product_image:  "img3.jpg",
          }
    ]
  },
  {
    product_id: 2,
    product_name: "my other cool product",
    product_desc: "desc of my other cool product",
    price: 20.00,
    product_images: [
          {
            product_image_id: 13,
            product_image:  "img21.jpg",
          },
          {
            product_image_id: 14,
            product_image:  "img22.jpg",
          },
          {
            product_image_id: 15,
            product_image:  "img23.jpg",
          }
    ]
  },

]

... and so on.

So it's clear that there's a parent-child relationship between products (the parent table) and product_images (the child table). Unfortunately, I have had no success creating an API function that will return an array of images which also has it's array of associated images. The ultimate goal is that I want to load multiple products and their associated images at once in one single API call, instead of getting the the products, then getting the images.

Here's a snapshot of the database tables:

CLICK HERE TO VIEW DATABASE TABLES

Here is the code that I have:

1. THE ROUTE

Send a text/json body to this route. It accepts a userId and a number of products to return:

app.route('/multiplerandomproducts/')
.post(products.get_multiple_random_products);

post body:  {user_id: <num>, product_count: <num>}

2. GETTING MY PRODUCTS

In my ProductsController.js Controller, I have the following:

exports.get_multiple_random_products = function(req,res) {
  var postData = new Product(req.body);
  Product.getMultipleRandomProducts(postData.data, function(err,products) {
    if(err) {
      res.send(err); 
    } else {
      res.json(products);
    }
  });
}

In my Products.js Model, I have the following:

// get a random product for the userId
Product.getMultipleRandomProducts = function getMultipleRandomProducts(postData, result) {
    var userId = postData.user_id;
    var productCount = postData.product_count;

    var sqlStr ="SELECT DISTINCT a.* FROM product a "
    + " INNER JOIN product_category b ON b.product_id = a.product_id "
    + " INNER JOIN user_category c ON c.category_id = b.category_id "
    + " WHERE c.user_id= ? "

    // get only products that the user hasn"t seen before
    if(process.env.APP_HIDESEENPRODS ==="1") {
        sqlStr +=" AND a.product_id NOT IN (SELECT c.product_id from seen_product c WHERE c.user_id = ?)";
    }

    sqlStr +=" ORDER BY RAND() LIMIT ?";

    sql.query(sqlStr, [userId, userId, productCount], function(err, res) {
        if(err) {
            result(err, null);
        } else {
            // hide from the use if this flag is set in the app
            if(!process.env.APP_HIDESEENPRODS ==="1") {
                insertUserSeenProduct(userId, res[0].product_id);
            }
            result(null,res);
        }
    });
}

I also have a ProductImages.js model, which as a method to get Images for a particular product, which looks like this:

ProductImage.getAllProductImages = function getAllProductImages(productId, result) {
        sql.query("Select * from product_image WHERE product_id = ? ", productId, function (err, res) {
                if(err) {
                    console.log("error: ", err);
                    result(null, err);
                }
                else {
                    result(null, res);
                }
            });   
    };

What I've tried:

So this is the version of my function that I've tried to implement. When I indicate that I want three records, I get an object with three null values, i.e., [null,null,null]. I think I'm close, just not seeing what I'm doing wrong....

Product.getMultipleRandomProducts = function getMultipleRandomProducts(postData, result) {
    var userId = postData.user_id;
    var productCount = postData.product_count;

    var sqlStr ="SELECT DISTINCT a.* FROM product a "
    + " INNER JOIN product_category b ON b.product_id = a.product_id "
    + " INNER JOIN user_category c ON c.category_id = b.category_id "
    + " WHERE c.user_id= ? "

    // get only products that the user hasn"t seen before
    if(process.env.APP_HIDESEENPRODS ==="1") {
        sqlStr +=" AND a.product_id NOT IN (SELECT c.product_id from seen_product c WHERE c.user_id = ?)";
    }

    sqlStr +=" ORDER BY RAND() LIMIT ?";

    sql.query(sqlStr, [userId, userId, productCount], function(err, res) {
        if(err) {
            result(err, null);
        } else {
            // hide from the use if this flag is set in the app
            if(!process.env.APP_HIDESEENPRODS ==="1") {
                insertUserSeenProduct(userId, res[0].product_id);
            }
            result(null, res.map((item) => {
                sql.query("Select * from product_image WHERE product_id = ? ", item.product_id, function (err, res2) {
                    if(err) {
                        console.log("error: ", err);
                        result(null, err);
                    }
                    else {
                        item.product_images = res2;
                    }
                });                  
            }));
        }

    });
}

The Problem that I'm running into:

So, I've tried to simply iterate through the returned array of products that I get back within and get the product images, which would work fine if this wasn't asynchronous. But when I try this, I return an object with three products (if I ask for three) but only one (the first) of those products has it's images assigned to it.

In summary, I want to return a single object that looks something like:

  • Product
    • image
    • image
    • image
  • Product
    • image
    • image
    • image
  • Product
    • image
    • image
    • image

So, can someone please, PLEASE tell me how I can do this? I'm still trying to wrap my head around callback functions and I'm about to have a brain aneurysm!

Thanks!!

0

Because the images of each product were fetch individually, the expected result is what your getting now. In fact, if you use a server (not local), chances are no images return to any of the products.

Use async/await and promisify your function to fetch the production images:

sql.query(sqlStr, [userId, userId, productCount], async function(err, res) {
    if(err) {
        result(err, null);
    } else {
        // hide from the use if this flag is set in the app
        if(!process.env.APP_HIDESEENPRODS ==="1") {
            insertUserSeenProduct(userId, res[0].product_id);
        }

        // Get the list of images thru promisify
        const getImages = function(item) {
            return new Promise( function(resolve, reject) {
                sql.query("Select * from product_image WHERE product_id = ? ", item.product_id, function (err, res2) {
                    if(err) {
                        console.log("error: ", err);
                        reject(result(null, err));
                    }
                    else {
                        item.product_images = res2;

                        resolve(item);
                    }
                });
            });
        };

        let products = [];

        const returnFalse = () => false;

        for (let i = 0; i < res.length; i++ ) {
            let product = await getImages(res[i]).catch(returnFalse);

            if (product) {
                products.push(product);
            }
        }

        result( null, products);
    }
});
  • Hey Irene! Thanks for the feedback. That looks "promising" (pun intended). However, when I ran this, what I got back was an array of three empty objects, like this... [ {}, {}, {} ] .... any thoughts? Thanks again! – Scotty Compton Mar 12 at 4:01
  • Edited my response and replace it with the good old passion for loop -: – Irene Mitchell Mar 12 at 4:09
  • YAAAAYYY!!! That worked! I'm not worthy lol. Thanks for that, you're my new hero! – Scotty Compton Mar 12 at 4:15

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.