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