I need to resolve all promises in an array of json, like this:
let list = [
{
id: 1,
data: new Promise((resolve, reject) => {
resolve('Cat')
})
},
{
id: 2,
data: new Promise((resolve, reject) => {
resolve('Dog')
})
},
{
id: 3,
data: new Promise((resolve, reject) => {
resolve('Mouse')
})
}
]
I use bluebird promises.
I used a for cycle to iterate all items, I would know if there was some way more elegant.
Expected result:
[ { data: 'Cat', id: 1 },
{ data: 'Dog', id: 2 },
{ data: 'Mouse', id: 3 } ]
This should work, Promise.all and array.map like the others, but the result is correct
let list = [
{
id: 1,
data: new Promise((resolve, reject) => {
resolve('Cat')
})
},
{
id: 2,
data: new Promise((resolve, reject) => {
resolve('Dog')
})
},
{
id: 3,
data: new Promise((resolve, reject) => {
resolve('Mouse')
})
}
]
Promise.all(list.map(item => item.data.then(data => ({...item, data}))))
// that's it, that's what you want, the rest is for show now
.then(results => {
console.log(results);
});
though, that's Native Promises ... you may want to look into Promise.props and/or Promise.map in bluebird for possibly simpler code yet
It could well be as simple as
Promise.map(list, Promise.props)
.then(results => {
console.log(results);
});
tested the above, and yes, it is that simple - the snippet below has my own version of Promise.map and Promise.props - and works (at least in this case) identically to bluebird
Promise.props = obj => {
const keys = Object.keys(obj);
return Promise.all(Object.values(obj)).then(results => Object.assign({}, ...results.map((result, index) => ({[keys[index]]: result}))));
};
Promise.map = (array, fn, thisArg) => Promise.all(Array.from(array, (...args) => fn.apply(thisArg, args)));
let list = [
{
id: 1,
data: new Promise((resolve, reject) => {
resolve('Cat')
})
},
{
id: 2,
data: new Promise((resolve, reject) => {
resolve('Dog')
})
},
{
id: 3,
data: new Promise((resolve, reject) => {
resolve('Mouse')
})
}
]
Promise.map(list, Promise.props)
.then(results => {
console.log(results);
});
Resolve Promise.all() to an actual usable array of JSON.
The problem: Promise.all() resolves to an array of resolved promises. To see why this is an issue, copy and paste this into your console:
var promise1 = fetch('https://swapi.dev/api/people/2'); // C-3PO
var promise2 = fetch('https://swapi.dev/api/people/3'); // R2-D2
Promise.all([promise1, promise2])
.then(res => console.log(res)); // resolved promises - trash...
See how there's no actual usable data in those resolved promises..? On the web, you need to call another promise to convert them to text or html or, most likely, beloved json. But how can you do this efficiently?
Well, then solution to our Promise.all() issue is... more Promise.all()!!!
Example:
var promise3 = fetch('https://swapi.dev/api/people/1'); // Luke
var promise4 = fetch('https://swapi.dev/api/people/4'); // Luke's daddy
Promise.all([promise3, promise4])
.then(res => Promise.all([res[0].json(), res[1].json()]))
.then(res => console.log(res)); // real data :)
Hope this helps someone! Thanks!
With Promise.all & some JavaScript.
let list = [];
let promise = Promise.all(list.map(entry => entry.data));
promise.then((results) => {
let final = results.map((result, index) => {
return {data: result, id: list[index].id};
});
console.log(final);
});
Promise.all may be a good fit here:
Promise.all(list.map(entry => entry.data)).then((resolved) => {
// ...
});
You just need create an array of promises. you can do it with for or map
let promises = []
for(var i=0; i < list.length; i++)
promises.push(list.data)
Promise.all(promises).then(resultList => { ... }).catch(err => { ... })
or simplest may with map
Promise.all(list.map(p => p.data)).then(resultList => { ... }).catch(err => { ... })
you can use lodash lib to minimize your code
const _ = require('lodash');
Promise.all(_.map(list, 'data')).then((data)=>{
list.map((item,ind)=>{item.data = data[ind]});
console.log(list);
})
Related
Let me first show you what the code looks like
Cart.getCompleteCart((cart)=>{
let products = []; *//this Array has to be filled*
totalPrice = cart.totalPrice;
for(let prod of cart.products){
Product.getProductDetails(prod.productId, productDetails => {
products.push({
product: productDetails,
productCount: prod.productCount
});
});
}
console.log("Products are ->", products); *//this line is running before for loop!*
}
console.log() is running before the for loop completes its work.
How to run "for loop" kind of in sync with the console.log() ?
I assume that Product.getProductDetails() is an async method (it looks like a query to an API).
console.log() is not "running before the for loop", it is just that the tasks performed by Product.getProductDetails() are being handled asynchronously.
Since it also seems that the Product.getProductDetails() method works with callbacks, one way to achieve your goal would be to promisify each call and then condensate all the promises into a single one by using Promise.all() method.
Something like this should do the trick:
Cart.getCompleteCart((cart) => {
const promises = [];
for (let prod of cart.products) {
promises.push(
new Promise((resolve) => {
Product.getProductDetails(prod.productId, (productDetails) => {
resolve({
product: productDetails,
productCount: prod.productCount
});
});
})
);
}
Promise.all(promises).then((products) => {
console.log('Products are ->', products);
});
});
Or:
Cart.getCompleteCart((cart) => {
Promise.all(
cart.products.map((prod) => {
return new Promise((resolve) => {
Product.getProductDetails(prod.productId, (productDetails) => {
resolve({
product: productDetails,
productCount: prod.productCount
});
});
});
})
).then((products) => {
console.log('Products are ->', products);
});
});
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);
I am using sequelizeJS. I have a Promise in Promise function. I want to make a Promise function to get data, then push this data into Array and return this Array.
I tried with this code but it's not success.
function sequelize_conversation (conversation_id, req) {
return new Promise((resolve, reject) => {
var response = []
for (let id of conversation_id) {
db.ConversationPerson.findAll({ where: {'conversation_id': id, 'user_id': { [Op.ne]: req.user.user_id }} })
.then(person => {
console.log(person) // results: { 'id': 1, 'name': 'John' }
response.push(person.dataValues)
})
}
resolve(response)
})
}
Result I get:
response = []
But I want to get:
response = [{ 'id': 1, 'name': 'John' }]
Please review my code and help me understand about Promise in Promise function. Thank you in advance!
promises imply asynchronous code ... you are resolving before any of the db.ConversationPerson.findAll has a chance to do anything, let alone complete
Your code is actually a lot simpler than you think
function sequelize_conversation (conversation_id, req) {
var promises = [];
for (let id of conversation_id) {
promises.push(
db.ConversationPerson.findAll({ where: {'conversation_id': id, 'user_id': { [Op.ne]: req.user.user_id }} })
.then(data => {
return data.dataValues
})
);
}
return Promise.all(promises);
}
Or if conversation_id is an actual Array, and adding a little bit of ES6+ goodness
const sequelize_conversation = (ids, req) => Promise.all(
ids.map(conversation_id =>
db.ConversationPerson.findAll({
where: {
conversation_id,
'user_id': {
[Op.ne]: req.user.user_id
}
}
})
.then(({dataValues}) => dataValues)
)
);
I am trying to write a Sequelize migration script win which I am trying to update my database but it is having many asynchronous operations (database queries and then updating database with particular id)
Here is my code
return db.organizationEntries
.findAll()
.then((entries) => {
return entries.forEach(entry => {
console.log(entry);
db.organizationEntries
.findAll({
attributes: [
[
db.sequelize.fn(
'MAX',
db.sequelize.col('organizationEntries.serial_number')
),
'maximum_serial_no'
]
],
where: {
organizationId: entry.organizationId
}
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no);
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 };
console.log(data)
//problem
db.organizationEntries.update(data, {
where: {
id: entry.id
}
})
.then((result) => {
console.log(result);
})
});
// promises.push(promise);
});
// return Promise.all(promises);
})
Actually what I am trying to do is I am trying to take the list of all orgEntries from the database and then I am finding maximum serial number for that organization_id and then updating that particular orgEntry and like this all these operations in a loop
Now the problem is coming all the things are going in order but after finding max_serial_no it is not updating the database and I am not able to resolve what I should do to make that asynchronous call work in this order
I think you can solve this in two ways:
Simultaneously Promises
In a following code I removed forEach in favor of Promise.all() and map()
The map() method create (and return) a new array with the results of calling a provided function on every element in the calling array.
Example:
let numbers = [1, 2, 3]
let doubledNumbers = numbers.map(n => n * 2)
// doubledNumbers [2, 4, 6]
The Promise.all() method take an array of Promises as argument and returns a single Promise that will be resolved when all promises will be resolved or rejected if one promise failed
Example:
let promise1 = findUserById(5)
let promise2 = findUserFriends(5)
Promise.all([promise1, promise2])
.then(values => {
// values: [user object, list of user friends]
})
Result:
db.organizationEntries.findAll()
.then(entries => {
return Promise.all(entries.map(entry => {
console.log(entry)
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
}))
})
.then(result => {
// result: Array of updated organizationEntries
console.log(result)
})
Step by step Promises with reduce() method
The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (from MDN web docs)
Example:
let items = [{ name: 'pencil', price: 2 }, { name: 'book', price: 10 }]
let total = items.reduce((total, item) => total += item.price, 0)
// total: 12
Result:
db.organizationEntries.findAll()
.then(entries => {
return entries.reduce((previousPromise, entry) => {
console.log(entry)
return previousPromise
.then(_ => {
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
.then(updatedEntry => {
console.log(updatedEntry)
})
}, Promise.resolve())
})
.then(result => {
// result: Last updated organization entry
console.log('finished')
})
How do I wrap this routine inside a Promise so that I only resolve when I get all the data?
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
});
})
});
EDIT: Any issues if I do it like this?
function getAccountsAllAtOnce() {
var accounts = [];
var required = 0;
var done = 0;
getAccounts(userId, accs => {
required = accs.length;
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
done = done + 1;
});
})
});
while(done < required) {
// wait
}
return accounts;
}
Let's put this routine into a separate function, so it is easier to re-use it later. This function should return a promise, which will be resolved with array of accounts (also I'll modify your code as small as possible):
function getAccountsWithTx(userId) {
return new Promise((resolve, reject) => {
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
// resolve after we fetched all accounts
if (accs.length === accounts.length) {
resolve(accounts);
}
});
});
});
});
}
The single difference is just returning a promise and resolving after all accounts were fetched. However, callbacks tend your codebase to have this "callback hell" style, when you have a lot of nested callbacks, and it makes it hard to reason about it. You can workaround it using good discipline, but you can simplify it greatly switching to returning promises from all async functions. For example your func will look like the following:
function getAccountsWithTx(userId) {
getAccounts(userId)
.then(accs => {
const transformTx = acc => getAccountTx(acc.id)
.then(tx => ({ tx, id: acc.id }));
return Promise.all(accs.map(transformTx));
});
}
Both of them are absolutely equivalent, and there are plently of libraries to "promisify" your current callback-style functions (for example, bluebird or even native Node util.promisify). Also, with new async/await syntax it becomes even easier, because it allows to think in sync flow:
async function getAccountsWithTx(userId) {
const accs = await getUserAccounts(userId);
const transformTx = async (acc) => {
const tx = getAccountTx(acc.id);
return { tx, id: acc.id };
};
return Promise.all(accs.map(transformTx));
}
As you can see, we eliminate any nesting! It makes reasoning about code much easier, because you can read code as it will be actually executed. However, all these three options are equivalent, so it is up to you, what makes the most sense in your project and environment.
I'd split every step into its own function, and return a promise or promise array from each one. For example, getAccounts becomes:
function getAccountsAndReturnPromise(userId) {
return new Promise((resolve, reject) => {
getAccounts(userId, accounts => {
return resolve(accounts);
});
});
};
And getAccountTx resolves to an array of { id, tx } objects:
function getAccountTransactionsAndReturnPromise(accountId) {
return new Promise((resolve, reject) => {
getAccountTx(account.id, (transactions) => {
var accountWithTransactions = {
id: account.id,
transactions
};
return resolve(accountWithTransactions);
});
});
};
Then you can use Promise.all() and map() to resolve the last step to an array of values in the format you desire:
function getDataForUser(userId) {
return getAccountsAndReturnPromise(userId)
.then(accounts=>{
var accountTransactionPromises = accounts.map(account =>
getAccountTransactionsAndReturnPromise(account.id)
);
return Promise.all(accountTransactionPromises);
})
.then(allAccountsWithTransactions => {
return allAccountsWithTransactions.map(account =>{
return {
id: account.id,
tx: tx
}
});
});
}
Need help related to promises. Please refer to details below, topic is more theoretical, I don't understand what flow should I use:
We have async function getDataFromUri(), which returns data, which gets filtered and saved to arr of objects, lets name it res;
For each campaign(object) inside of array res I want send async request which will store products images of campaign to object. As result I should have ONE array res where ALL filtered data (campaign name, campaign images) is stored;
I need smth like:
[{
name: "Somename",
images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
},{
name: "Somename",
images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
}
]
Do some other actions with RES array.
This function gets campaigns:
function getDataFromUri(uri) {
return new Promise((resolve, reject) => {
request.get(uri, (err, res, body) => {
if(err || res.statusCode !== 200 ) {
reject(handleErr(err));
} else {
resolve(body);
}
});
});
}
This function gets images of campaign:
function getProductsOfCampaign(id) {
var productsImagesLinks = [];
return new Promise((resolve, reject) => {
getDataFromUri(`SOME_URI/${id}.json`)
.then((json) => {
var productsList = JSON.parse(json).products;
resolve (productsList.map((product) => product.imgSrc));
}).catch((e) => {
throw new Error(e);
})
});
}
Here I met problem:
getDataFromUri(someLink) //Get campaings;
.then((result) => {
//NOT WORKING FOREACH
result.forEach((item, i) => {
item.images = getProductsOfCampaign(item.id);
})
return result;
})
.then((result) => {
//Do something else to array with images;
});
How can I force next after forEach .then() expression to wait all images URLs to be saved?
I tried Promise.all(), but seems have lack of knowledge on how to implement it correct way.
I will really appreciate if you help me resolve this case. Thank you.
Observe that:
item in forEach is a copy.
getProductsOfCampaign returns a Promise.
The web is a best-effort service.
Do this:
getDataFromUri(someLink) // Get campaigns
.then(result => {
var promises = result.map(item =>
getProductsOfCampaign(item.id)
.then(products => {
item.images = products;
return item;
})
// 3: Best-effort service
.catch(() => {})
);
return Promise.all(promises);
}).then(items => {
console.log(items);
// Do something else to array of items with images
});
Other readers can test for correctness with this:
function getDataFromUri(someLink) {
return new Promise((resolve) => {
setTimeout(resolve, 1000, [{id: 1}, {id: 2}]);
})
}
function getProductsOfCampaign(id) {
return new Promise((resolve) => {
setTimeout(resolve, 1000, id * id);
})
}
var someLink = '';
Thanks to Benjamin Gruenbaum for suggesting that .catch(() => {}) can be used with Promise.all for a best-effort service.
let campaigns = null;
getDataFromUri(someLink) //Get campaings;
.then((result) => {
campaigns = result;
let pImages = []
result.forEach((item, i) => {
pImages.push(getProductsOfCampaign(item.id));
});
return Promise.all(pImages);
})
.then((images) => {
campaigns.forEach((campaign, index) => {
campaign.images = images[index];
});
// ... Do something else to array with images;
});