问题
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