I have a shopping cart system where order of requests is quite important. The system has to be able to create users before it adds items to their user account. To do this I'm using promises and I'm trying to chain them so that they appear one after the other. Maybe my understanding is flawed but I'm not getting this to work.
processUsers function. Returns a promise. The order of this function is important as well because we need to create all users accounts in the order before we start connecting them together (there is a link between a child and parents in this system)
this.createUsers and this.createUserRelations are both functions that return a list of Promises
processUsers(child, parents) {
return new Promise((resolve, reject) => {
Promise.all(
this.createUsers(child, parents)
).then((userResponse) => {
Promise.all(
this.createUserRelations(
child,
parents
)
).then((relationResponse) => {
resolve(relationResponse)
}).catch(reject)
}).catch(reject)
})
}
This works. The order of this is correct. I'm testing this by making the create_or_update function on the server sleep for 5 seconds and indeed the createUserRelations function waits for that.
After I've created the users I use the same logic to add items to each user
/**
* process a single ticket
* @param {Array} ticket
*/
processTicket(ticket) {
var self = this
return new Promise((resolve, reject) => {
var ticketUser = {}
const userPromises = ticket.filter(
(t) => t.item.item_type === ITEM_TYPE_TICKET
).map((m) => {
ticketUser = m.user
return self.processUsers(m.user, m.parents)
})
const itemPromises = ticket.map(
(t) => {
if(t.item.item_type === ITEM_TYPE_BUS) {
t.user = ticketUser
}
return self.processItem(t)
}
)
Promise.all(userPromises).then((data) => {
Promise.all(itemPromises).then((data) => {
resolve(data)
}).catch(reject)
}).catch(reject)
})
}
This doesn't work. The itemPromises doesn't wait for userPromises to finish and therefor I get an error because the server cannot find the user to link the item with. I know that Promise.all() doesn't run promises in serial but I thought it would start running userPromises and once they are resolved it would run itemPromises. It seems it does not do this. I've tried multiple other things such as using p-queue.
Here is the processItem function
processItem(item) {
// returns a Promise
return users.add_order_item(
this.sessionUser.id,
item.user.email,
item.item.id,
item.delivery
)
}
And finally the main function handling tickets for the entire order
processOrder() {
const items = this.orderSessionItems
const reduced = this.groupBy(
items, (i) => i.reference_number)
var self = this
const promises = Object.keys(reduced).map((key, index) => {
return self.processTicket(reduced[key])
})
return Promise.all(promises)
}
UPDATE: Turns out I was indeed misunderstanding how Promises work. When mapping over the list (two times) in processTicket the processItem promise is invoked immediately. I thought this was not the case but it's called before I do Promise.all().
What I ended up with is this
processTicket(ticket) {
return new Promise((resolve, reject) => {
var self = this
var ticketUser = {}
const userPromises = ticket.filter(
(t) => t.item.item_type === ITEM_TYPE_TICKET
).map((m) => {
ticketUser = m.user
return self.processUsers(m.user, m.parents)
})
Promise.all(userPromises).then(() => {
const itemPromises = ticket.map(
(t) => {
if(t.item.item_type === ITEM_TYPE_BUS) {
t.user = ticketUser
}
return self.processItem(t)
}
)
Promise.all(itemPromises)
.then((data) => resolve(data))
.catch(reject)
}).catch(reject)
})
}
and now it works!