Node JS:如何为循环承诺

时间:2018-02-20 03:42:29

标签: javascript node.js express

我正在尝试创建一个NodeJS应用。下面是管理员创建新产品时应该调用的代码。大多数代码都可以工作,但是只有在执行其余代码(DB函数是异步的)之后才能渲染视图。我已将许多代码包含在promises中(以使某些块以正确的顺序执行)和控制台日志(以查明问题)。

我想指出console.dir(rejProducts)正好在console.log(111)以下的日志和空数组。此外,在for循环的结束括号之前添加console.dir(rejProducts)会记录一个空数组。谢谢!如果您需要更多信息,请与我们联系。

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

  });

});

编辑:我对代码做了一些更改。似乎util.promisify没有被识别为函数。我正在使用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.");
  })
});

}

1 个答案:

答案 0 :(得分:1)

一些提示:如果你的功能超过100行,你可能会在功能上做很多事情。

如果您必须以获得产品的方式从您的请求中获取数据,那么请编写更好的客户端代码(产品应该是一系列不需要消毒的产品对象)。服务器上需要验证,因为您永远不会信任客户端发送给您的内容。但是,查看清理,您甚至不相信您的客户端脚本会向您发送信息。

尝试编写一些小功能并尝试使用它们。

使用.map将类型a映射到类型b(例如req.body到产品数组,如示例代码中所示)。

使用.map的结果作为Promise.all

的参数

使用util.promisify将回调函数更改为返回承诺的函数,因为您使用旧版本的节点我已经添加了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)
    )
  });
}