在 JavaScript 中,如何停止执行丢失 Promise.race 的 Promise?

In JavaScript, how to stop execution of a Promise which lost a Promise.race?

提问人:Youri Malleck-Ahmed 提问时间:9/17/2023 最后编辑:BergiYouri Malleck-Ahmed 更新时间:9/30/2023 访问量:101

问:

在使用 时,我没有找到一种直接的方法来停止执行 Promise。让我解释一下。AbortController

为了在 timeout 参数定义的时间限制内等待异步函数_aget_Cards,我创建了两个 Promise,并按照下面的代码块对它们进行了竞赛。

async function xxx (userCN :string, timeout :number) :Promise<CardDetailsWithCCBal[]|unknown> {
    const controller = new AbortController()
    const raced_promise =  Promise.race ([
        new Promise ((resolve, reject) => {
            _aget_Cards (resolve, reject, userCN, controller)
        }),
        new Promise ( (resolve, reject) => {
            setTimeout ( () => reject('CONTROLLER_TIMEOUT'), timeout)
        })
    ])
    raced_promise.catch((error) => {
        console.log ('Error on raced promise detected')
        if (error === 'CONTROLLER_TIMEOUT') {
            console.log ('Detected error is a CONTROLLER_TIMEOUT ... Sending "abort" signal to controller for _aget_Cards')
            controller.abort()
        }
    })
    return raced_promise
}

您会注意到,如果 Promise 的时间限制提前完成,我提出了一个特定的“CONTROLLER_TIMEOUT”错误。在处理该特定错误时,我使用 .reject('CONTROLLER_TIMEOUT')controller.abort()

回调和 AbortController 实例在第一个 Promise 中作为参数传递。resolverejectcontroller_aget_Cards (resolve, reject, userCN, controller)

_aget_Cards 函数中,我按照下面的代码块设置了 。controller.signal.addEventListenerdoAbort = true

async function _aget_Cards (resolve :(value :unknown) => void, reject :(reason ?:any) => void, userCN :string, controller :AbortController)  { 
    
    let doAbort = false
    controller.signal.addEventListener ("abort", () => {
        console.log ('Controller received "abort" signal ... aborting ...')
        doAbort = true
    })  

    let start_time = Date.now()
    if (doAbort) return
    const cardDetails_lists :CardDetails[][] = await Promise.all ( [
        aget_CardDetails_List (userCN, true),  // Get Primary cards
        aget_CardDetails_List (userCN, false)  // Get Secondary cards
    ])
    console.log(`List of Primary and Secondary Cards obtained in ${(Date.now()-start_time)/1000}s`)
    
    start_time = Date.now()
    if (doAbort) return
    const cardDetails_list :CardDetails[] = [...cardDetails_lists[0], ...cardDetails_lists[1]]
    cardDetails_list.sort ( (a, b) => {
        let a_sortVal :string = `${a.cardType}${a.cardNo}`
        let b_sortVal :string = `${b.cardType}${b.cardNo}`
        return (a_sortVal < b_sortVal) ? -1 : (a_sortVal > b_sortVal) ? 1 : 0 
    })

    if (doAbort) return
...

您会注意到,我一直在不止一个地方使用中止执行。我没有找到一种直接的方法,在检测到中止时强制返回。if (doAbort) return

用 inside 拒绝 promise 不会影响 _aget_Cards 的执行。引发错误也是如此。rejectcontroller.signal.addEventListener

您的帮助将不胜感激。

JavaScript 超时 es6-promise 取消 abortcontroller

评论

0赞 trincot 9/17/2023
“stop execution of a Promise”:承诺不执行。它是一个对象。
0赞 Bergi 9/17/2023
该怎么办?请向我们展示它的代码aget_CardDetails_List

答:

0赞 Bergi 9/17/2023 #1

我一直在不止一个地方使用中止执行。我没有找到一种直接的方法,在检测到中止时强制返回。if (doAbort) return

是的,它不像在发出信号时将中止信号注册到执行中止执行(通常在 期间)那样急促。JavaScript 没有这样的功能:-/async functionawait

手动测试意味着您可以控制控制流可能中止的位置。在您的例子中,例如,您正在排序之前和之后进行测试,以便排序本身不会被“中断”——它要么全部执行,要么什么都不执行。

但是,您可以大大简化代码:

  • 不要在一定时间后拒绝的承诺中使用。只需在一定时间后中止即可Promise.racecontroller
  • 与其手动构造 and do and yourself,不如使用 AbortSignal.timeout() 辅助函数AbortControllersetTimeoutcontroller.abort()
  • 避免异步函数作为执行器传递给新的 Promise!您只需调用函数即可获得承诺,并且不必在正确的时间处理调用 /resolve()reject()
  • 只传递给应该停止的函数,而不是整个。将控制器传递给应该能够停止其他函数的函数。AbortSignalAbortController
  • 不要使用您在中止信号的事件侦听器中为自己设置的布尔变量。只需参考 signal.aborteddoAbort
  • 不要使用 ,而是调用 signal.throwIfAborted(),它将抛出中止原因并让它作为异常向上传播调用堆栈if (signal.aborted) return

所以你的代码变成了

async function xxx(userCN: string, timeout: number): Promise<CardDetailsWithCCBal[]|unknown> {
    const signal = AbortSignal.timeout(timeout)
    return _aget_Cards(userCN, signal).catch(error => {
        console.log ('Error on promise detected')
        if (error === signal.reason) {
            console.log ('Detected error is a CONTROLLER_TIMEOUT...')
        }
        throw e
    })
}

async function _aget_Cards (userCN: string, signal: AbortSignal) {
    let start_time = Date.now()
    signal.throwIfAborted()
    const cardDetails_lists: CardDetails[][] = await Promise.all([
        aget_CardDetails_List(userCN, true),  // Get Primary cards
        aget_CardDetails_List(userCN, false)  // Get Secondary cards
    ])
    console.log(`List of Primary and Secondary Cards obtained in ${(Date.now()-start_time)/1000}s`)
    
    start_time = Date.now()
    signal.throwIfAborted()
    const cardDetails_list: cardDetails_lists.flat().sort((a, b) => {
        let a_sortVal: string = `${a.cardType}${a.cardNo}`
        let b_sortVal: string = `${b.cardType}${b.cardNo}`
        return (a_sortVal < b_sortVal) ? -1 : (a_sortVal > b_sortVal) ? 1 : 0 
    })
    signal.throwIfAborted()
    …
}

最后,您不需要经常调用它。同步代码执行时不会触发信号,因此,如果它在排序之前尚未中止,也不会在排序后立即中止。您通常只需要此方法在等待检查信号是否同时被触发。更好的是,当信号触发时,你承诺提前拒绝,将信号传递给你正在调用的函数,并在其中处理它:signal.throwIfAborted()await

async function _aget_Cards (userCN: string, signal: AbortSignal) {
    let start_time = Date.now()
    const cardDetails_lists: CardDetails[][] = await Promise.all([
        aget_CardDetails_List(userCN, true, signal),  // Get Primary cards
//                                          ^^^^^^
        aget_CardDetails_List(userCN, false, signal)  // Get Secondary cards
//                                           ^^^^^^
    ])
    console.log(`List of Primary and Secondary Cards obtained in ${(Date.now()-start_time)/1000}s`)
    
    start_time = Date.now()
    const cardDetails_list: cardDetails_lists.flat().sort((a, b) => {
        let a_sortVal: string = `${a.cardType}${a.cardNo}`
        let b_sortVal: string = `${b.cardType}${b.cardNo}`
        return (a_sortVal < b_sortVal) ? -1 : (a_sortVal > b_sortVal) ? 1 : 0 
    })
    …
}
-1赞 Youri Malleck-Ahmed 9/30/2023 #2

感谢 Bergi 提请我注意 Promise 构造函数的反模式,以及您为简化代码而提出的其他评论,例如消除 Promise.race 来处理超时。

我查看了我的代码并对其进行了简化,这样我甚至不必使用 AbortController

入口点异步函数aget_Cards由客户端调用,如以下代码所示。

// Call made by Client
let start_time = Date.now()
aget_Cards('9999',8000)
    .then ( (cardDetailsWithCCBal_list :CardDetailsWithCCBal[]|unknown) => {
        console.log (cardDetailsWithCCBal_list)
        console.log(`\n\nAll finished in ${(Date.now()-start_time)/1000}s`)
    })
    .catch ( (error :Error) => {
        console.log (`Error is : ${error}`)
    })

异步函数aget_Cards定义如下。 我在这个函数中创建一个 Promise,并将回调 resolvereject 传递给较低级别的函数 _aget_Cards

async function aget_Cards (userCN :string, timeout :number) :Promise<CardDetailsWithCCBal[]|unknown> {
    return new Promise ((resolve, reject) => {
        _aget_Cards (resolve, reject, userCN, timeout)
    })
}

在较低级别的函数_aget_Cards中,我使用 setTimeout,它调用作为参数传递的 reject 回调函数,并设置一个 stop 布尔变量。我必须将拒绝回调函数作为参数传递给此函数,因为在 setTimeout 中调用 Promise.reject() 将不起作用。 此外,我捕获任何错误并拒绝错误。

async function _aget_Cards (resolve :(value :unknown) => void, reject :(reason ?:any) => void, userCN :string, timeout :number)  { 
    
    let start_time = Date.now()
    let stop = false
    
    setTimeout ( () => {
            reject('CONTROLLER_TIMEOUT')
            stop = true
        }
        , timeout
    )

    try {
        if (stop) return
        const cardDetails_lists :CardDetails[][] = await Promise.all ( [
            aget_CardDetails_List (userCN, true),  // Get Primary cards
            aget_CardDetails_List (userCN, false)  // Get Secondary cards
        ])
        console.log(`List of Primary and Secondary Cards obtained in ${(Date.now()-start_time)/1000}s`)
        if (stop) return

        start_time = Date.now()
        const cardDetails_list :CardDetails[] = [...cardDetails_lists[0], ...cardDetails_lists[1]]
        cardDetails_list.sort ( (a, b) => {
            let a_sortVal :string = `${a.cardType}${a.cardNo}`
            let b_sortVal :string = `${b.cardType}${b.cardNo}`
            return (a_sortVal < b_sortVal) ? -1 : (a_sortVal > b_sortVal) ? 1 : 0 
        })
        console.log(`List of Primary and Secondary Cards have been sorted`)
        if (stop) return
        // ...
        console.log(`Fulfilling promise _aget_Cards ...`)
        resolve(cardDetailsWithCCBal_list)
    } catch (error) {
        reject (error)
    }
}

我测试了上述内容,它有效。

评论

0赞 Bergi 9/30/2023
"将回调 resolvereject 传递给较低级别的异步函数_aget_Cards- 这仍然是相同的反模式。另外,为什么要避免使用中止信号?这在这里非常合适。我答案中的代码对你不起作用吗?
0赞 Youri Malleck-Ahmed 10/2/2023
按照建议,我使用单个 Promise。退货无效。它是异步的,只是因为它里面有等待代码。如果在 _aget_Cards 内部引发了错误,而我没有引发错误,则 Promise 的 catch 不会捕获该错误。这就是我通过和_aget_Cards的原因,它起作用了。由于我没有测试中止信号,因此根据上述解释,我无法确定 Promise 的捕获是否会捕获该信号。无论如何,我现在的实现是有效的,我感谢您的建议。async function _aget_CardsrejectresolverejectthrowifAborted