提问人:Youri Malleck-Ahmed 提问时间:9/17/2023 最后编辑:BergiYouri Malleck-Ahmed 更新时间:9/30/2023 访问量:101
在 JavaScript 中,如何停止执行丢失 Promise.race 的 Promise?
In JavaScript, how to stop execution of a Promise which lost a Promise.race?
问:
在使用 时,我没有找到一种直接的方法来停止执行 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 中作为参数传递。resolve
reject
controller
_aget_Cards (resolve, reject, userCN, controller)
在 _aget_Cards 函数中,我按照下面的代码块设置了 。controller.signal.addEventListener
doAbort = 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 的执行。引发错误也是如此。reject
controller.signal.addEventListener
您的帮助将不胜感激。
答:
我一直在不止一个地方使用中止执行。我没有找到一种直接的方法,在检测到中止时强制返回。
if (doAbort) return
是的,它不像在发出信号时将中止信号注册到执行中止执行(通常在 期间)那样急促。JavaScript 没有这样的功能:-/async function
await
手动测试意味着您可以控制控制流可能中止的位置。在您的例子中,例如,您正在排序之前和之后进行测试,以便排序本身不会被“中断”——它要么全部执行,要么什么都不执行。
但是,您可以大大简化代码:
- 不要在一定时间后拒绝的承诺中使用。只需在一定时间后中止即可
Promise.race
controller
- 与其手动构造 and do and yourself,不如使用
AbortSignal.timeout()
辅助函数AbortController
setTimeout
controller.abort()
- 避免将
异步函数
作为执行器传递给新的 Promise
!您只需调用函数即可获得承诺,并且不必在正确的时间处理调用 /resolve()
reject()
- 只传递给应该停止的函数,而不是整个。将控制器传递给应该能够停止其他函数的函数。
AbortSignal
AbortController
- 不要使用您在中止信号的事件侦听器中为自己设置的布尔变量。只需参考
signal.aborted
doAbort
- 不要使用 ,而是调用
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
})
…
}
感谢 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,并将回调 resolve 和 reject 传递给较低级别的函数 _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)
}
}
我测试了上述内容,它有效。
评论
resolve
和 reject
传递给较低级别的异步
函数_aget_Cards
。- 这仍然是相同的反模式。另外,为什么要避免使用中止信号?这在这里非常合适。我答案中的代码对你不起作用吗?
async function _aget_Cards
reject
resolve
reject
throwifAborted
评论
aget_CardDetails_List