【问题标题】:Cannot get Promise to evaluate in sequence无法让 Promise 依次评估
【发布时间】:2019-04-07 20:12:44
【问题描述】:

我有一个购物车系统,其中请求的顺序非常重要。系统必须能够在将项目添加到用户帐户之前创建用户。为此,我使用了 Promise,并尝试将它们链接起来,以便它们一个接一个地出现。也许我的理解有缺陷,但我没有让它发挥作用。

processUsers 函数。返回一个承诺。这个函数的顺序也很重要,因为我们需要在开始连接之前按顺序创建所有用户帐户(在这个系统中孩子和父母之间有一个链接)

this.createUsers 和 this.createUserRelations 都是返回 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)
        })
    }

这行得通。这个顺序是正确的。我正在通过使服务器上的 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() 不会串行运行 Promise,但我认为它会开始运行 userPromises,一旦解决,它将运行 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)
    }

更新:原来我确实误解了 Promise 的工作原理。在 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)
        })
    }

现在它可以工作了!

【问题讨论】:

  • 你应该很少需要new这样的承诺:第一个函数应该只是return Promise.all(...).then(() => Promise.all(...)).catch(reject);如果你从回调中返回一个 Promise,它就会成为链的一部分。
  • @jonrsharpe 是的,我就是这么想的。但是,如果我这样做,则 processUsers 函数的顺序不起作用。 Promise.all(something).then(Promise.all(somethingElse))。我需要像你一样先将匿名函数传递给它吗? Promise.all(something).then(() => Promise.all()).catch(reject)
  • 是的,否则它将无法工作,因为.then 期望回调,而不是承诺(回调肯定会返回承诺)
  • 在您的示例中,您将第二个 promise.all 作为回调函数传递给第一个,这没有意义;它需要从回调函数返回,就像我的示例中的匿名箭头函数。
  • 嗯,当然可以。我的错。非常感谢您指出这一点!

标签: javascript asynchronous promise es6-promise


【解决方案1】:

我知道 Promise.all() 不会串行运行 Promise,但我认为它会开始运行 userPromises,一旦解决,它就会运行 itemPromises。

不,promise 没有“运行”,Promise.all 没有“启动”任何东西。承诺只是您可以等待的东西,Promise.all 将这些东西组合成一个您可以等待的承诺。

当您致电processItem() 时,工作就开始了,您确实会立即致电。如果您在 then 回调中进行调用,它将等待 userPromises 开始处理项目。

顺便说一句,还要避免Promise constructor antipattern

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 本身不会被调用,直到我调用“then”或者直到我在 Promise.all 中使用它,但是 processItem 在我调用它时立即运行。
猜你喜欢
  • 2017-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多