简体   繁体   English

节点JS:如何承诺循环

[英]Node JS: How to Promise For Loop

I'm trying to create a NodeJS app. 我正在尝试创建一个NodeJS应用。 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). 大多数代码都能正常工作,但是只有在其余代码执行完之后(DB函数是异步的),我才无法呈现视图。 I've wrapped much of the code in promises (to make certain blocks execute in the right order) and console logs (to pinpoint problems). 我已经将许多代码包装在Promise(使某些块按正确的顺序执行)和Console日志(以查明问题)中。

I'd like to point out that the console.dir(rejProducts) just below console.log(111) logs and empty array. 我想指出console.log(111)下方的console.dir(rejProducts) console.log(111)日志和空数组。 Also, adding console.dir(rejProducts) just before the end bracket of the for loop logs an empty array. 另外,在for循环的结尾括号之前添加console.dir(rejProducts)记录一个空数组。 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. 似乎util.promisify没有被识别为函数。 I am using Node 9.4. 我正在使用节点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.");
  })
});

}

Some tips: if your function is over 100 lines you may be doing to much in the function. 一些提示:如果您的函数超过100行,则可能在函数中做了很多事情。

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). 使用.map将类型a映射为类型b(例如,将req.body映射到示例代码中的产品数组)。

Use the result of .map as an argument to Promise.all .map的结果用作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: 使用util.promisify将回调函数更改为返回promise的函数,因为您使用的是旧版节点,所以我添加了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)
    )
  });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM