【问题标题】:How to call Promise function in loop and save its return value如何在循环中调用 Promise 函数并保存其返回值
【发布时间】:2016-07-30 18:27:25
【问题描述】:

我创建了一个名为getBasketObject 的promise 函数(using bluebird)。此函数需要一个篮子作为参数,然后从中返回一个新的 basketObject

basketObject 有一些变量,例如 tax, total, shippingproductItems。现在,productItems 对象中有price, name, quantity 可用的属性,但它没有productImageLink 可用。

为了获得productImageLink,我对端点进行了新的异步调用,该端点将为我获取产品图像对象。 Image Endpoint 也被实现为 Promise。

现在,我遍历productLineItems 并获取name, price, quantity 等属性的值,最后调用获取图像。

现在,如果我添加

basketObj["products"][productId]["productImageSrc"] = smallImage[0]; 我的对象从未被修改过,在最终输出中我没有得到图片链接。

发生这种情况是因为我的getBasketObject 在异步调用发生之前返回了值。为了解决这个问题,我添加了resolve(basketObj);,但这会立即返回并且不在循环中。

那么,循环遍历我的产品项目并获取所有产品的图片链接的正确方法是什么。

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

    });
};

如果我使用resolve(basketObject),我的最终对象看起来像

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

即使productLineItems 有多个产品,您也可以看到它只获取一个产品对象

【问题讨论】:

  • productLineItems 是数组还是对象?使用for in 循环表明它不是一个数组,但因为你称它为index 而不是key,所以看起来它是一个数组。
  • @t.niese:它是一个对象,我将其更正为使用key 以避免混淆。谢谢

标签: javascript express promise nodes bluebird


【解决方案1】:

首先,您的resolve(basketObj) 无效,因为您多次调用resolve 为您的Promise,但您只能调用一次。

您还应该避免将string 用作错误,但始终使用真正的错误(不仅使用promise,而且始终使用javascript)。

您可以在 Promise 链中传递 Object.keys(productLineItems),而不是 for in 循环,然后使用 .each 而不是 for in 循环。

这样就可以返回product.getProductObject引入的Promise。

你可以这样重写:

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

如果你不想使用.each,你可以这样写:

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

【讨论】:

  • promiseObj.each 不是标准 ES6 Promise 对象的一部分。 .each 的另一个缺点是按顺序运行 Promise,而不是并行运行,例如 Promise.all do
  • @NickRassadin OP 声明使用了bluebird,但是是的,它可以用不同的方式编写。
  • @RanRag 根据尼克的评论更新了该答案,因此它还包含一个没有.each的示例
  • @RanRag 不是真的,但我会看一下 anit 模式:Promise anti patternsPromise Patterns & Anti-PatternsPromise Anti-patterns。当你有深度嵌套(类似于回调)时,你做错了什么,应该考虑你可以改变什么。
【解决方案2】:

循环未完成,因为您在第一次迭代中解决了您的承诺。但是您需要等待product.getProductObject 的所有异步调用完成。这里Promise.all 来帮忙。

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

请确保 product.getProductObject(productId) 确实进行异步调用

【讨论】:

  • 如果您在then 的回调中调用resolve,那么您很可能以错误的方式使用了Promises。这并不意味着您的解决方案不起作用,只是您不应该那样使用 Promise。它类似于The Forgotten Promise
  • 不,你错了。这里方法返回所有并发承诺都将完成的承诺。你不能以任何其他方式做。这与被遗忘的承诺模式相去甚远。
  • 模式return new Promise(function(resolve, reject) { /*...*/ Promise.all(asyncCalls).then(function(value) { resolve(basketObj); }, function(reason) { reject(reason); }) 是一个糟糕的模式,这就是您在答案中所写的。 return new Promise 不是必需的,或者Promise.all 之前的某些内容是错误的。这就是为什么它类似于The Forgotten Promise
  • 是的,毫无疑问,将return new Promise... 更改为return Promise.all(...) 绝对是正确的方法。但它需要对原始代码进行彻底的返工,不符合教育中畅通无阻的要求。
猜你喜欢
  • 1970-01-01
  • 2020-11-28
  • 1970-01-01
  • 2019-11-06
  • 2021-10-09
  • 2017-01-02
  • 2020-01-25
  • 2020-01-10
  • 1970-01-01
相关资源
最近更新 更多