Unable to resolve promise chain - javascript

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

Related

Add a Promise for each array item to a `Promise.all()`

I have the following code where I iterate through a list with names and use each name to fetch in this case the respective house. This works fine, but I want to include all promises to a Promise.all() - how can I dynamically add a Promise for each name to a Promise.all()? What's good practice here?
list = [
'Name1',
'Name2',
'Name3'
];
this.houses = new BehaviorSubject<any>([]);
const promises = this.greatHouses.map(name => {
let house;
return this.fetchHouse(name).then((result: any[]) => {
house = result[0];
return result[0];
}).then((result: any) => {
return this.fetchLord(result.currentLord);
}).then((result: any) => {
const currentLord = Characters.default.find(char => char.characterName === result.name);
return [{...house, currentLord}];
});
});
Promise.all(promises).then(values => {
console.log(values);
});
fetchHouse(name: string) {
return this.http.get(`${this.housesUrl}`, {
params: {
name
}
}).toPromise();
}
fetchLord(url: string) {
return this.http.get(url).toPromise();
}
Don't use Promises at all. Learn to work with Observables if you're using Angular and its HttpClient.
greatHouses = [
'Name1',
'Name2',
'Name3'
];
// Construct and array of Observables that fetch your data
const houses$ = this.greatHouses.map(name =>
this.fetchHouse(name).pipe(
// map result to house
map((result: any[]) => result[0]),
// map house to an Observable that fetches the lord and return the required data
switchMap((house: any) => this.fetchLord(house.currentLord).pipe(
// map lord to house with currentLord
map((lord: any) => {
const currentLord = Characters.default.find(char => char.characterName === lord.name);
return { ...house, currentLord };
})
))
)
);
// Combine an array of completing Observables with forkJoin to one Observable
forkJoin(houses$).subscribe(houses => console.log('houses', houses))
// Return Observables and handle errors with `catchError` directly where they occur.
fetchHouse(name: string) {
return this.http.get(`${this.housesUrl}`, {
params: {
name
}
}).pipe(
catchError(error => {
console.error('fetchHouse failed', error);
return of(null);
})
);
}
fetchLord(url: string) {
return this.http.get(url).pipe(
catchError(error => {
console.error('fetchLord failed', error);
return of(null);
})
);
}

How can I wait for the loop to Complete?

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

Promise in Promise function: Can't push data into Array

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

Using values within different arrays

I have 2 arrays retrieved from a db and stripe respectively. Both data were mapped using a common field. In attempting to process charges, I get this error StripeInvalidRequestError ---- There is currently another in-progress request using this Stripe token. Meaning the loop definitely runs twice. So my question is how do I efficiently achieve using data from both arrays without looping twice?
Any thoughts, please?
array1 = [
{ totalCharge: 10000 },
{ totalCharge: 30000 },
]
array2 = [
{ customer: 63667363,
source: "kssgjag_98899"
},
{ customer: 767897889
source: "hkhdgd_93762"
},
]
for (let x of array1){
for (let y of array2){
stripe.charges.create({
amount: x.totalCharge,
currency: "usd",
customer: y.id,
source: y.source
}, function(err, charge) {}
}
}
You're only visiting each combination of x and y once (total of four combinations), but from subsequent comments it appears that you don't want to do that, you want to make two charges: 10000 for kssgjag_98899 and 30000 for hkhdgd_93762.
The simplest thing to do is probably build yourself a single array of charges to make, then process it one charge at a time.
Building the array:
if (array1.length !== array2.length) {
throw new Error("Arrays should be of the same length");
}
const charges = array1.map(({totalCharge}, index) {
const {id, source} = array2[index];
return {
amount: totalCharge,
currency: "usd",
customer: id,
source
};
});
Processing them one at a time: Nicolas Takashi told me in a comment on a now-deleted answer that in addition to having a callback, stripe.charges.create also returns a promise. If so, you can use the classic promise reduce trick:
const promiseForResult = charges.reduce(
(p, charge) => p.then(() => stripe.charges.create(charge)),
Promise.resolve()
);
That will ensure that you only have one call to stripe.charges.create happening at any given time.
If it's okay for the calls to overlap (but from your error it sounds like it isn't), you'd use Promise.all instead:
const promiseForResult = Promise.all(
charges.map(charge => stripe.charges.create(charge))
);
If it's not true that stripe.charges.create returns a promise, you can easily give yourself a wrapper that does:
const createCharge = charge => new Promise((resolve, reject) => {
strip.charges.create(charge, function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
and then either one-at-a-time:
const promiseForResult = charges.reduce(
(p, charge) => p.then(() => createCharge(charge)),
Promise.resolve()
);
or all at once:
const promiseForResult = Promise.all(charges.map(createCharge));
Here is a working example that results in a new array array3 that has the zipped up result.
const array1 = [
{ totalCharge: 10000 },
{ totalCharge: 30000 },
]
const array2 = [
{ customer: 63667363,
source: "kssgjag_98899"
},
{ customer: 767897889,
source: "hkhdgd_93762"
},
]
const array3 = [];
for (let x of array1) {
for (let y of array2) {
array3.push({
amount: x.totalCharge,
currency: "usd",
customer: y.customer,
source: y.source
});
}
}
console.log(JSON.stringify(array3));
Output:
[
{"amount":10000,"currency":"usd","customer":63667363,"source":"kssgjag_98899"},
{"amount":10000,"currency":"usd","customer":767897889,"source":"hkhdgd_93762"},
{"amount":30000,"currency":"usd","customer":63667363,"source":"kssgjag_98899"},
{"amount":30000,"currency":"usd","customer":767897889,"source":"hkhdgd_93762"}
]
You will need to make a charge request from your server for each of these, waiting for the previous request to complete before starting the next:
function processCharges(charges: any[], index = 0) {
stripe.charges.create(charges[index], (err, charge) => {
if (err) {
// handle error
}
index++;
if (index < charges.length) {
processCharges(charges, index);
}
});
}

Promise.all does not wait for all the promise to finish firestore data read

I have below data structure that will be added to the firestore collection as document
{
".......": "......." //few more properties
"productInfo": {
"0": {
"code": "SC05",
"price": 400,
"productId": "asUsd1HPEPOo2itiKxdash",
".......": "......." //few more properties
"addOns": [{
"aid": "4casedasgdfdgfas",
"price": "50",
"......": "....." //few more properties
}]
}
}
}
So I will be having array of products object and each product will have array of addon object.
I am trying to retrieve price from db before saving it as document and hence I've written .onCreate function for that particular table. To retrieve price on the product level and addon level, I am having 2 promises as below:
const productPromises = [];
_.map(products, (product) => {
productPromises.push(new Promise((resolve, reject) => {
db.doc(`product/${product.productId}`).get().then(docData => {
return resolve({
productId: product.productId,
halfkgprice: docData.data().halfkgprice,
price: docData.data().price
});
}).catch(reason => {
return reject(reason);
});
}));
});
const addonPromise = [];
_.map(products, (product) => {
addonPromise.push(new Promise((resolve, reject) => {
if (product.addOns !== undefined && product.addOns !== null) {
return _.map(product.addOns, (addon) => {
return db.doc(`addons/${addon.aid}`).get().then((addonData) => {
return resolve({
price: addonData.data().price
});
}).catch(reason => {
return reject(reason);
})
});
} else {
return resolve();
}
}));
});
and then I do have
Promise.all([productPromises, addonPromise]).then(result => {
//do something with result
});
But this will not wait any of the promise to resolve. When I log the result I get as below.
console.log(result) will show [ [ Promise { <pending> } ], [ Promise { <pending> } ] ] and doesn't wait for them to resolve
My question is, why is not waiting for all the promises to resolve? Is there any problem with the way I am returning data from firestore get query?
productPromises and addonPromise are arrays of promises, so the array you pass to Promise.all is an array of arrays, not an array of promises - try
Promise.all([...productPromises, ...addonPromise]).then
Also, avoid the Promise constructor anti-pattern
_.map(products, (product) => {
productPromises.push(
db.doc(`product/${product.productId}`).get().then(docData => {
return ({
productId: product.productId,
halfkgprice: docData.data().halfkgprice,
price: docData.data().price
});
})
);
});
You need to concat 2 arrays to make it work as expected
Promise.all(productPromises.concat(addonPromise)).then(result => {
//do something with result
});
map builds an array of returned values, so you can just write:
const productPromises = _.map(products, product => db.doc(`product/${product.productId}`).get()
.then(docData => ({
productId: product.productId,
halfkgprice: docData.data().halfkgprice,
price: docData.data().price
})
);

Categories