У меня есть система корзины покупок, где порядок запросов очень важен. Система должна иметь возможность создавать пользователей, прежде чем добавлять элементы в их учетную запись. Для этого я использую промисы и пытаюсь связать их в цепочку, чтобы они появлялись одно за другим. Может быть, мое понимание ошибочно, но я не заставляю это работать.
Функция processUsers. Возвращает обещание. Порядок этой функции также важен, потому что нам нужно создать все учетные записи пользователей в порядке, прежде чем мы начнем соединять их вместе (в этой системе есть связь между ребенком и родителями).
this.createUsers и this.createUserRelations — это функции, которые возвращают список промисов.
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)
})
}
Это работает. Порядок этого правильный. Я проверяю это, заставляя функцию create_or_update на сервере спать в течение 5 секунд, и функция createUserRelations действительно ждет этого.
После того, как я создал пользователей, я использую ту же логику для добавления элементов каждому пользователю.
/**
* 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)
})
}
Это не работает. ItemPromises не ждет завершения userPromises, и поэтому я получаю сообщение об ошибке, потому что сервер не может найти пользователя, с которым можно связать элемент. Я знаю, что Promise.all() не запускает обещания в последовательном порядке, но я думал, что он начнет выполнять пользовательские промисы, и как только они будут разрешены, он запустит itemPromises. Вроде этого не делает. Я пробовал несколько других вещей, таких как использование p-queue.
Вот функция processItem
processItem(item) {
// returns a Promise
return users.add_order_item(
this.sessionUser.id,
item.user.email,
item.item.id,
item.delivery
)
}
И, наконец, основная функция обработки тикетов на весь заказ
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)
}
ОБНОВЛЕНИЕ: Оказывается, я действительно неправильно понимал, как работают промисы. При отображении списка (два раза) в processTicket обещание processItem вызывается немедленно. Я думал, что это не так, но он вызывается до того, как я выполню Promise.all().
Что я закончил с этим
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)
})
}
и теперь это работает!
@jonrsharpe да, я так и думал. Однако порядок функции processUsers не работает, если я это сделаю. Обещание.все(что-то).затем(Обещание.все(что-тоЕще)). Нужно ли мне сначала передать анонимную функцию, как вы? Promise.all(что-то).then(() => Promise.all()).catch(отклонить)
Да, иначе это не сработает, так как .then
ожидает обратный вызов, а не обещание (этот обратный вызов точно может вернуть обещание)
В вашем примере вы передаете второй promise.all как функция обратного вызова первому, что не имеет смысла; это должна быть вернулся из функция обратного вызова, как анонимная функция стрелки в моем примере.
Ааа ок конечно. Виноват. Большое спасибо, что указали на это!
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.
Нет, обещания не "запускаются", Promise.all
ничего не "запускает". Обещание — это то, чего вы можете дождаться, и Promise.all
объединяет несколько этих вещей в одно обещание, которого вы можете дождаться.
Работа начинается, когда вы звоните processItem()
, и вы звоните сразу же. Если вы выполняете вызовы внутри обратного вызова then
, он будет ждать userPromises
, прежде чем начнет обрабатывать элементы.
Кстати, также избегайте Promise
конструктор антипаттерн:
processTicket(ticket) {
var ticketUser = {}
const userPromises = ticket.filter((t) =>
t.item.item_type === ITEM_TYPE_TICKET
).map((m) => {
ticketUser = m.user
return this.processUsers(m.user, m.parents)
})
return Promise.all(userPromises).then(() => {
// ^^^^^^
const itemPromises = ticket.map((t) => {
if (t.item.item_type === ITEM_TYPE_BUS) {
t.user = ticketUser
}
return this.processItem(t)
})
return Promise.all(itemPromises)
// ^^^^^^
})
}
абсолютно вы правы. Я неправильно понял, я думал, что сам Promise не будет вызван, пока я не назову «тогда» или пока я не использую его в Promise.all, но processItem запускается, как только я его вызываю.
Вам редко нужно
new
выполнять такие обещания: первая функция должна быть простоreturn Promise.all(...).then(() => Promise.all(...)).catch(reject)
; если вы возвращаете обещание из обратного вызова, оно становится частью цепочки.