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.");
})
});
}