Node JS: How to Promise For Loop

Wallie

I'm trying to create a NodeJS app. Below is code that is supposed to be called when an admin creates a new product. Most of the code works, but I'm having trouble rendering the view only after the rest of the code has executed (the DB functions are asynchronous). I've wrapped much of the code in promises (to make certain blocks execute in the right order) and console logs (to pinpoint problems).

I'd like to point out that the console.dir(rejProducts)just below console.log(111) logs and empty array. Also, adding console.dir(rejProducts) just before the end bracket of the for loop logs an empty array. Thanks! Please let me know if you need more information.

app.post('/products/new', function (req, res, next) {
  // Async function: find all categories
  Category.find(function(err, categories) {
    // Hidden count that tells num products to be created by form
    var numProducts = req.body[`form-item-count`];
    // Array of all rejected products adds
    var rejProducts = [];

    var promiseLoopProducts = new Promise(function(resolve, reject) {
      var promiseProducts = [];
      // Loop through all addded products
      for (let i = 0; i < numProducts; i++) {
        var promiseProductCheck = new Promise(function(resolve, reject) {
          var name = validate.sanitize(req.body[`name_${i}`]);
          var category = validate.sanitize(req.body[`category_${i}`]);
          var price = validate.sanitize(req.body[`price_${i}`].replace(/\$/g, ""));
          var stock = validate.sanitize(req.body[`stock_${i}`]);
          var image = validate.sanitize(req.body[`image_${i}`]);
          var description = validate.sanitize(req.body[`description_${i}`]);

          var rejProduct;
          var rejFields = { 'name': name, 'category': category, 'price': price,
                            'stock': stock, 'image': image,
                            'description': description };
          var rejErrors = {};

          var productData = {
            name: name,
            category: category,
            price: price,
            stock: stock,
            image: image,
            description: description
          };
          var promiseCategoryCheck = new Promise(function(resolve, reject) {
            if (ObjectId.isValid(category)) {
              var promiseCategoryCount = new Promise(function(resolve, reject) {
                Category.count({'_id': category}, function(error, count) {
                  rejErrors['name'] = validate.isEmpty(name);
                  if (count == 0) rejErrors['category'] = true;
                  rejErrors['price'] = !validate.isPrice(price);
                  rejErrors['stock'] = !validate.isInt(stock);

                  if( validate.isEmpty(name) || !validate.isPrice(price) ||
                      count == 0 || !validate.isInt(stock) ) {
                    rejProduct = { 'fields': rejFields, 'errors': rejErrors };
                    rejProducts.push(rejProduct);
                    console.dir(rejProducts);
                    console.log(count);
                    return resolve();
                  }
                  else {
                    Product.create(productData, function (error, product) {
                      if (error) return next(error);
                      console.log(77);
                        console.dir(rejProducts);
                      return resolve();
                    });
                  }
                  if (error) return next(error);
                });
              });
              promiseCategoryCount.then(function() {
                console.dir(rejProducts);
                return resolve();
              });
            } else {
              rejErrors['category'] = true;
              rejProduct = { 'fields': rejFields, 'errors': rejErrors };
              rejProducts.push(rejProduct);
              console.dir(rejProducts);
            }
          });
          promiseCategoryCheck.then(function() {
            console.dir(rejProducts);
            promiseProducts.push(promiseProductCheck);
            console.log(promiseProductCheck);
            console.log(promiseProducts);
            return resolve();
          });
        });
        promiseProductCheck.then(function() {
          console.log(106);
          console.dir(rejProducts);
        });
      }
      Promise.all(promiseProducts).then(function() {
        console.log(111);
        console.dir(rejProducts); // Empty array!
        return resolve();
      });
    });

    promiseLoopProducts.then(function() {
      console.log(118);
      console.dir(rejProducts); // Empty array!
      res.render('products/new', { categories: categories, rejProducts: rejProducts });
    });

  });

});

Edit: I've made some changes to the code. There it seems like util.promisify is not being recognized as a function. I am using Node 9.4.

module.exports = function(app){

const util = require('util');
require('util.promisify').shim();
const validate = require('../functions/validate');

const Category = require('../db/categories');
const Product = require('../db/products');

var ObjectId = require('mongoose').Types.ObjectId;
//Category.find as function that returns a promise
const findCategories = util.promisify(Category.find);

const countCategories = (categoryId) => {
  util.promisify(Category.count({'_id': categoryId}));
};

const bodyToProduct = (body, i) => {
  var name = validate.sanitize(body[`name_${i}`]);
  var category = validate.sanitize(body[`category_${i}`]);
  var price = validate.sanitize(body[`price_${i}`].replace(/\$/g, ""));
  var stock = validate.sanitize(body[`stock_${i}`]);
  var image = validate.sanitize(body[`image_${i}`]);
  var description = validate.sanitize(body[`description_${i}`]);
  return {
    name: name,
    category: category,
    price: price,
    stock: stock,
    image: image,
    description: description
  };
};

app.post('/products/new', function (req, res, next) {
  // Async function: find all categories
  return findCategories()
  .then(
    categories=>{
      // Hidden count that tells num products to be created by form
      var numProducts = req.body[`form-item-count`];
      // Array of all rejected products adds
      var rejProducts = [];
      return Promise.all(
        Array.from(new Array(numProducts),(v,i)=>i)
        .map(//map [0...numProducts] to product object
          i=>bodyToProduct(req.body,i)
        )
        .map(
          product => {
            var rejErrors;
            var rejName = validate.isEmpty(name);
            var rejCategory;
            var rejPrice = !validate.isPrice(price);
            var rejStock = !validate.isInt(stock);
            if (ObjectId.isValid(product.category)) {
              return countCategories()
              .then(
                count=> {
                  if (count == 0) rejCategory = true;

                  if(rejName || rejCategory || rejPrice || rejStock ) {
                    rejErrors = {
                      name: rejName,
                      category: rejCategory,
                      price: rejPrice,
                      stock: rejStock
                    }
                    rejProduct = { 'fields': product, 'errors': rejErrors };
                    rejProducts.push(rejProduct);
                    console.dir(rejProducts);
                    console.log(count);
                  } else {
                    Product.create(productData, function (error, product) {
                      if (error) return next(error);
                      console.log(77);
                        console.dir(rejProducts);
                    });
                  }
                }
              ).catch(function() {
                console.log("Count function failed.");
              });
            } else {
              rejCategory = true;
              rejErrors = {
                name: rejName,
                category: rejCategory,
                price: rejPrice,
                stock: rejStock
              }
              rejProduct = { 'fields': product, 'errors': rejErrors };
              rejProducts.push(rejProduct);
              console.dir(rejProducts);
            }
          }
        )
      ).then(function() {
        res.render('products/new', { categories: categories, rejProducts: rejProducts });
      }).catch(function() {
        console.log("Promise all products failed.");
      });
    }
  ).catch(function() {
    console.log("Find categories failed.");
  })
});

}
HMR

Some tips: if your function is over 100 lines you may be doing to much in the function.

If you have to get data from your request the way you get products of it then write better client side code (products should be an array of product objects that should not need to be sanitized). Validation is needed on the server because you an never trust what the client is sending you. But looking at the sanitize you don't even trust what your client script is sending you.

Try to write small functions that do a small thing and try to use those.

Use .map to map type a to type b (for example req.body to array of products as in the example code).

Use the result of .map as an argument to Promise.all

Use util.promisify to change a callback function into a function that returns a promise, since you are using an old version of node I've added an implementation of promisify:

var promisify = function(fn) {
  return function(){
    var args = [].slice.apply(arguments);
    return new Promise(
      function(resolve,reject){
        fn.apply(
          null,
          args.concat([
            function(){
              var results = [].slice.apply(arguments);
              (results[0])//first argument of callback is error
                ? reject(results[0])//reject with error
                : resolve(results.slice(1,results.length)[0])//resolve with single result
            }
          ])
        )
      }
    );
  }
};

module.exports = function(app){
  const validate = require('../functions/validate');
  const Category = require('../db/categories');
  const Product = require('../db/products');
  var ObjectId = require('mongoose').Types.ObjectId;
  //Category.find as function that returns a promise
  const findCategories = promisify(Category.find.bind(Category));
  const countCategories = (categoryId) => {
    promisify(Category.count.bind(Category))({'_id': categoryId});
  };
  const createProduct = promisify(Product.create.bind(Product));
  const REJECTED = {};
  const bodyToProduct = (body, i) => {
    var name = validate.sanitize(body[`name_${i}`]);
    var category = validate.sanitize(body[`category_${i}`]);
    var price = validate.sanitize(body[`price_${i}`].replace(/\$/g, ""));
    var stock = validate.sanitize(body[`stock_${i}`]);
    var image = validate.sanitize(body[`image_${i}`]);
    var description = validate.sanitize(body[`description_${i}`]);
    return {
      name: name,
      category: category,
      price: price,
      stock: stock,
      image: image,
      description: description
    };
  };

  const setReject = product => {
    var rejErrors;
    var rejName = validate.isEmpty(product.name);
    var rejCategory;
    var rejPrice = !validate.isPrice(product.price);
    var rejStock = !validate.isInt(product.stock);
    const countPromise = (ObjectId.isValid(product.category))
      ? countCategories()
      : Promise.resolve(0);
    return countPromise
    .then(
      count => {
        if (count == 0) rejCategory = true;

        if (rejName || rejCategory || rejPrice || rejStock) {
          rejErrors = {
            type:REJECTED,
            name: rejName,
            category: rejCategory,
            price: rejPrice,
            stock: rejStock
          }
          return rejErrors;
        }
        return false;
      }
    );
  };

  const productWithReject = product =>
    Promise.all([
      product,
      setReject(product)
    ]);

  const saveProductIfNoRejected = ([product,reject]) =>
    (reject===false)
      ? Product.create(product)
        .catch(
          err=>({
            type:REJECTED,
            error:err
          })
        )
      : reject;

  app.post('/products/new', function (req, res, next) {
    // Async function: find all categories
    return findCategories()
    .then(
      categories => {
        // Hidden count that tells num products to be created by form
        var numProducts = req.body[`form-item-count`];
        // Array of all rejected products adds
        var rejProducts = [];
        return Promise.all(
          Array.from(new Array(numProducts), (v, i) => i)
            .map(//map [0...numProducts] to product object
              i => bodyToProduct(req.body, i)
            )
            .map(
              product=>
                productWithReject(product)
              .then(saveProductIfNoRejected)
            )
        ).then(
          results =>
            res.render(
              'products/new',
              { 
                categories: categories,
                rejProducts: results.filter(x=>(x&&x.type)===REJECTED)
              }
            )
        ).catch(
          error=>
            console.log("Promise all products failed.",error)
        );
      }
    ).catch(
      error=>
        console.log("Find categories failed.",error)
    )
  });
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

in node.js, how to use bluebird promise with a for-loop

From Dev

node.js promise loop architecture with bluebird

From Dev

How to add promise.all in Node.js Sequelize findOrCreate in loop?

From Dev

how to use Promise with express in node.js?

From Dev

How to embed a promise in a node.js module?

From Dev

Waiting for Promise before moving to next iteration in a loop in Node.js

From Dev

Promise not working in Node JS

From Dev

How to handle for loop in node.js?

From Dev

How to create an interruptible loop in node.js

From Dev

How predictable is the event loop in Node.JS

From Dev

How predictable is the event loop in Node.JS

From Dev

how to break from a loop in node.js?

From Dev

How to simplify nested if loop in node js querying

From Dev

How to extend q.promise in node.js?

From Dev

How to actually use Q promise in node.js?

From Dev

How to use Typescript Async/ await with promise in Node JS FS Module

From Dev

Promise middleware in node js callback functions how to use multiple then?

From Dev

node.js redis and how to use promise when using a module

From Dev

How to get values from a promise with node.js without .then function

From Dev

Promise middleware in node js callback functions how to use multiple then?

From Dev

How to check in an node.js C++ addon, if a promise is resolved

From Dev

How to handle loop and promise?

From Dev

node js request promise, scraping

From Dev

node js synchronous for loop

From Dev

Node js infinite loop

From Dev

execute a for loop in node js

From Dev

Synchronous for loop in node js

From Dev

Dosynchronous Loop in node js

From Dev

How to break loop inside a promise?

Related Related

HotTag

Archive