Promise.all is designed for pretty much this exact use-case:
// A dummy "Product" with a dummy "getProductDetails" implementation
// so that we can have a working test example
let Product = {
getProductDetails: (productId, callback) => {
setTimeout(() => {
callback({ type: 'productDetails', productId });
}, 100 + Math.floor(Math.random() * 200));
}
};
// This is the function you're looking for:
let getCompleteCart = async (cart) => {
return Promise.all(cart.products.map(async ({ productId, productCount }) => ({
product: await new Promise(resolve => Product.getProductDetails(productId, resolve)),
productCount
})));
}
let exampleCart = {
products: [
{ productId: 982, productCount: 1 },
{ productId: 237, productCount: 2 },
{ productId: 647, productCount: 5 }
]
};
getCompleteCart(exampleCart).then(console.log);
A breakdown of getCompleteCart:
let getCompleteCart = async (cart) => {
return Promise.all(cart.products.map(async ({ productId, productCount }) => ({
product: await new Promise(resolve => Product.getProductDetails(productId, resolve)),
productCount
})));
}
Promise.all (mdn) is purpose-built to wait for every promise in an Array of promises to resolve
We need to supply an Array of Promises to Promise.all. This means we need to convert our Array of data (cart.products) to an Array of Promises; Array.protoype.map is the perfect tool for this.
The function provided to cart.products.map converts every product in the cart to an Object that looks like { product: <product details>, productCount: <###> }.
The tricky thing here is getting the value for the "product" property, since this value is async (returned by a callback). The following line creates a promise that resolves to the value returned by Product.getProductDetails, using the Promise constructor (mdn):
new Promise(resolve => Product.getProductDetails(productId, resolve))
The await keyword allows us to convert this promise into the future value it results in. We can assign this future value to the "product" attribute, whereas the "productCount" attribute is simply copied from the original item in the cart:
{
product: await new Promise(resolve => Product.getProductDetails(productId, resolve)),
productCount
}
In order to run console.log at the right time we need to wait for all product details to finish compiling. Fortunately this is handled internally by getCompleteCart. We can console.log at the appropriate time by waiting for getCompleteCart to resolve.
There are two ways to do this:
Using Promise.prototype.then (mdn):
getCompleteCart(exampleCart).then(console.log);
Or, if we're in an async context we can use await:
let results = await getCompleteCart(exampleCart);
console.log(results);