How to call Promise function in loop and save its return value

核能气质少年 提交于 2019-12-12 03:08:07

问题


I have created a promise function(using bluebird) called getBasketObject. This function expects a basket as an argument and than return a new basketObject out of it.

basketObject has some variables like tax, total, shipping and productItems. Now, the productItems object has price, name, quantity properties available in it but it doesn't have productImageLink available.

In order to get productImageLink I make a new async call to an endpoint which will get me the product images object. Image Endpoint is also implemented as a Promise.

Now, I loop over the productLineItems and get the value of attributes like name, price, quantity and finally make the call to get image.

Now, if I add

basketObj["products"][productId]["productImageSrc"] = smallImage[0]; my object is never modified and in the final output I don't get image link.

This happens because my getBasketObject returned value before the async call happened. In order to tackle this I added resolve(basketObj); but this returns immediately and am out of the loop.

So, what is the correct way to loop over my product items and get image links for all the products.

exports.getBasketObject = function(basket) {

    return new Promise(function(resolve, reject){

        if (!basket){
            reject("Please give valid basket");
        }
        var basketObj = {};

        if ('order_total' in basket && basket.order_total) {
            basketObj.total = basket.order_total;
        } else if ('product_total' in basket && basket.product_total) {
            basketObj.total = basket.product_total;
        }

        var productLineItems = basket.product_items;
        basketObj["products"] = {};
        for (var key in productLineItems) {
            var productItem = productLineItems[key];
            var productId = productItem.product_id;

            //Async call to get Product Object
            product.getProductObject(productId).then(function(productObj){

                basketObj["products"][productId] = {};
                basketObj["products"][productId]['productQuantity'] = productItem.quantity;
                basketObj["products"][productId]["productName"] = productItem.item_text;
                basketObj["products"][productId]["productPrice"] = productItem.base_price;
                //If promise resolved, get images
                var imageObject = product.getProductImages(productObj[0]);
                var smallImage = imageObject['small'];
                basketObj["products"][productId]["productImageSrc"] = smallImage[0];
                resolve(basketObj); //Acts as a return
            });
        }

    });
};

If I use resolve(basketObject) my final Object looks like

  {
    "total": 95.99,
    "tax": "N/A",
    "shipping": "N/A",
    "products": {
        "701642890706": {
            "productQuantity": 1,
            "productName": "Novelty Stitch Belted Cardigan",
            "productPrice": 95.99,
            "productImageSrc": "image.png"
        }
    }
}

You can see it gets only one product object even if productLineItems has multiple products


回答1:


First of all your resolve(basketObj) is not valid because you call resolve multiple times for you Promise, but you must call it only once.

You also should avoid to use string as errors, but always use real error (not only with promises but all the time in javascript).

Instead of your for in loop you can pass Object.keys(productLineItems) in your Promise chain, and then use the .each instead of the for in loop.

That way you can return the Promise introduced by product.getProductObject.

You could rewrite it like that:

exports.getBasketObject = function(basket) {

  var basketObj = {};
  var productLineItems;

  return Promise.resolve(basket)
  .then(function(basket) {
    if( !basket ) {
      throw new Error("Please give valid basket");
    }
    productLineItems = basket.product_items;
  })
  .then(function() {
    if ( 'order_total' in basket && basket.order_total) {
      basketObj.total = basket.order_total;
    } else if ( 'product_total' in basket && basket.product_total) {
      basketObj.total = basket.product_total;
    }

    basketObj.products = {};

    //return the all keys of the productLineItems to be able to iterate over it using promises
    return Object.keys(productLineItems);
  })
  .each(function(key) {
    var productItem = productLineItems[key];
    var productId = productItem.product_id;
    basketObj.products[productId] = {};
    basketObj.products[productId].productQuantity = productItem.quantity;
    basketObj.products[productId].productName = productItem.item_text;
    basketObj.products[productId].productPrice = productItem.base_price;

    //Async call to get Product Object
    return product.getProductObject(productId).then(function(productObj) {
      //If promise resolved, get images
      var imageObject = product.getProductImages(productObj[0]);
      var smallImage = imageObject.small;
      basketObj.products[productId].productImageSrc = smallImage[0];
    });
  })
  .then(function() {
    //  return the basketObj after all  product.getProductObject resolved
    return basketObj;
  });
};

If you don't want to use .each you can write it that way:

exports.getBasketObject = function(basket) {

  var basketObj = {};
  var productLineItems;

  return Promise.resolve(basket)
  .then(function(basket) {
    if( !basket ) {
      throw new Error("Please give valid basket");
    }
    productLineItems = basket.product_items;
  })
  .then(function() {
    if ( 'order_total' in basket && basket.order_total) {
      basketObj.total = basket.order_total;
    } else if ( 'product_total' in basket && basket.product_total) {
      basketObj.total = basket.product_total;
    }

    basketObj.products = {};

    var promises = [];

    Object.keys(productLineItems).forEach(function(key) {
      var productItem = productLineItems[key];
      var productId = productItem.product_id;
      basketObj.products[productId] = {};
      basketObj.products[productId].productQuantity = productItem.quantity;
      basketObj.products[productId].productName = productItem.item_text;
      basketObj.products[productId].productPrice = productItem.base_price;


      promises.push(
        product.getProductObject(productId).then(function(productObj) {
          //If promise resolved, get images
          var imageObject = product.getProductImages(productObj[0]);
          var smallImage = imageObject.small;
          basketObj.products[productId].productImageSrc = smallImage[0];
        });
      );
    });

    return Promise.all(promises);
  })
  .then(function() {
    return basketObj;
  });
};



回答2:


Loop did not finished because you resolve your promise at the first iteration. But you need to wait all the async calls of product.getProductObject to finished. Here Promise.all to help.

...
var asycnCalls = [];
for (var index in productLineItems) {

    ...
    //Async call to get Product Object
    asyncCalls.push(
        product.getProductObject(productId).then(function(productObj){
            //If promise resolved, get images
            var imageObject = product.getProductImages(productObj[0]);
            var smallImage = imageObject['small'];
            basketObj["products"][productId]["productImageSrc"] = smallImage[0];                
        })
    )
} //end of for loop

Promise.all(asyncCalls).then(function(value) { 
    resolve(basketObj); //Acts as a return
}, function(reason) {
    reject(reason);
});

And please make sure product.getProductObject(productId) does really make async calls



来源:https://stackoverflow.com/questions/36517419/how-to-call-promise-function-in-loop-and-save-its-return-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!