文章介绍了JavaScript中的AbortController API,该API允许开发者中止任何操作,如HTTP请求、事件监听器、数据流等。文章详细描述了AbortController的使用方法,包括如何创建控制器实例、使用signal属性、监听abort事件以及实现中止逻辑。此外,文章还介绍了AbortSignal的静态方法,如timeout和any,并展示了如何在不同场景中应用AbortController API。最后,文章强调了AbortController API的灵活性和在取消请求、移除事件监听器、中止流或教给任何逻辑以可取消性方面的应用。
AbortController是JavaScript中的全局类,用于终止任何操作。它提供了一种机制,让开发者可以在任何时候中止正在进行的操作,如HTTP请求、事件监听器、数据流等。
使用AbortController需要先创建控制器实例,然后通过signal属性提供AbortSignal实例给需要中断的API。调用controller.abort()方法会触发signal的abort事件,从而中止操作。
AbortSignal提供了静态方法timeout和any,方便创建带有超时或组合多个取消信号的signal。
文章展示了如何在事件监听器、HTTP请求、Node.js的http模块以及流中使用AbortController。此外,还介绍了如何将其应用于Drizzle ORM事务中,以便一次取消多个事务。
每次abort事件都会伴随着中止的原因,开发者可以根据不同的异常原因采取不同的响应措施。AbortController API允许在abort方法中提供一个自定义原因,以便更好地处理中止错误。
前言
介绍了 JavaScript 中的 AbortController API,它允许开发者中止任何操作,如 HTTP 请求、事件监听器、数据流等,以及如何使用 AbortSignal 来实现超时、取消和流控制,以及如何在自定义逻辑中实现取消能力。今日前端早读课文章由 @飘飘翻译分享。
译文从这开始~~
今天,我想谈谈一个你可能忽略了的标准 JavaScript API。它叫做 AbortController 。
AbortController 是什么?
AbortController 是 JavaScript 中的全局类,可用于终止任何操作。
【早阅】VoidZero Inc.:下一代 JavaScript 工具链
这是使用方法:
const controller = new AbortController()
controller.signal
controller.abort()
一旦创建了控制器实例,你会得到两样东西:
到目前为止一切顺利。但实际的中止逻辑在哪里?这就是美妙之处 —— 它由使用者定义。中止处理就是监听 abort 事件,并以适合特定逻辑的方式实现终止操作。
controller.signal.addEventListener('abort', () => {
// Implement the abort logic.
})
让我们一起探索一下那些内置支持 AbortSignal 的标准 JavaScript API。
使用方法
事件监听器
当为某个事件添加监听器时,可以提供一个 abort signal ,以便在 abort 发生时自动移除该监听器。
const controller = new AbortController()
window.addEventListener('resize', listener, { signal: controller.signal })
controller.abort()
调用 controller.abort()
将从窗口中移除 resize 监听器。这是一种极其优雅的处理事件监听器的方式,因为你不再需要抽象监听器函数,只需将其提供给 .removeEventListener()
即可 。
// const listener = () => {}
// window.addEventListener('resize', listener)
// window.removeEventListener('resize', listener)
const controller = new AbortController()
window.addEventListener('resize', () => {}, { signal: controller.signal })
controller.abort()
如果应用程序的其他部分负责移除监听器,那么使用匿名内部类作为监听器会更加方便。
让我感到非常惊喜的一件事情是,我发现可以用一个简单的 removeEventListener()
方法来移除多个事件监听器!
useEffect(() => {
const controller = new AbortController()
window.addEventListener('resize', handleResize, {
signal: controller.signal,
})
window.addEventListener('hashchange', handleHashChange, {
signal: controller.signal,
})
window.addEventListener('storage', handleStorageChange, {
signal: controller.signal,
})
return () => {
// Calling `.abort()` removes ALL event listeners
// associated with `controller.signal`. Gone!
controller.abort()
}
}, [])
在上面的例子中,我在 React 中添加了一个 useEffect()
钩子,引入了各种具有不同目的和逻辑的事件监听器。请注意,在清理函数中,通过一次调用 controller.abort()
来移除所有添加的监听器。真棒!
【第3312期】 JavaScript 如何中止Promise
获取请求
fetch()
函数也支持 AbortSignal !当 signal 上的 abort 事件被触发时, fetch()
函数返回的请求承诺将被拒绝,中止正在进行的请求。
function uploadFile(file: File) {
const controller = new AbortController()
// Provide the abort signal to this fetch request
// so it can be aborted anytime be calling `controller.abort()`.
const response = fetch('/upload', {
method: 'POST',
body: file,
signal: controller.signal,
})
return { response, controller }
}
在这里, uploadFile()
函数会发起一个 POST /upload
请求,返回与之关联的 response promise,同时提供一个 controller 引用,以便在任何时候都可以取消该请求。例如,如果我需要取消正在上传的文件,而用户点击了 “取消” 按钮,那么这个功能就非常有用。
Node.js 中的 http 模块也支持 signal 属性!
AbortSignal 类还提供了一些静态方法,以简化 JavaScript 中的请求处理。
AbortSignal.timeout
你可以使用 AbortSignal.timeout()
的静态方法作为一种简便的方法来创建一个 signal,会在经过一定超时时间后触发 abort 事件。如果你只想在请求超时后取消请求,就不需要创建 AbortController 。
fetch(url, {
// Abort this request automatically if it takes
// more than 3000ms to complete.
signal: AbortSignal.timeout(3000),
})
AbortSignal.any
类似于如何使用 Promise.race()
来按先到先处理多个 Promise,你可以利用 AbortSignal.any()
静态方法将多个取消 signal 组合成一个。
const publicController = new AbortController()
const internalController = new AbortController()
channel.addEventListener('message', handleMessage, {
signal: AbortSignal.any([publicController.signal, internalController.signal]),
})
在上面的例子中,我引入了两个中止控制器。公共的控制器暴露给我的代码的使用者,允许他们触发中止,从而导致 message 事件监听器被移除。然而,内部的控制器则允许我在不干扰公共中止控制器的情况下,也移除那个监听器。
【第3234期】fetchLater:JS全新API支持关闭页面时安全发送网络请求
如果向 AbortSignal.any()
提供的任何终止信号触发了终止事件,那么这个父信号也将发送中止事件。此后的任何其他终止事件将被忽略。
Streams
你可以使用 AbortController 和 AbortSignal 来取消流。
const stream = new WritableStream({
write(chunk, controller) {
controller.signal.addEventListener('abort', () => {
// Handle stream abort here.
})
},
})
const writer = stream.getWriter()
await writer.abort()
这个 WritableStream 控制器暴露了 signal 属性,它与旧的 AbortSignal 属性相同。这样,我就可以调用 writer.abort()
,这将向上传递到 controller.signal
的 write()
方法中的取消事件。
使任何事情都能被中止
我最喜欢的关于 AbortController API 的部分是它极其灵活。灵活到你可以教给它任何逻辑,让它可以被中断!
拥有这样的超级能力,不仅可以自己打造更好的体验,还可以提升对不支持中断 / 取消的第三方库的使用效果。事实上,我们现在就来做这件事。
让我们在 Drizzle ORM 事务中添加 AbortController ,以便一次取消多个事务。
import { TransactionRollbackError } from 'drizzle-orm'
function makeCancelableTransaction(db) {
return (callback, options = {}) => {
return db.transaction((tx) => {
return new Promise((resolve, reject) => {
// Rollback this transaction if the abort event is dispatched.
options.signal?.addEventListener('abort', async () => {
reject(new TransactionRollbackError())
})
return Promise.resolve(callback.call(this, tx)).then(resolve, reject)
})
})
}
}
makeCancelableTransaction()
函数接受一个数据库实例作为参数,并返回一个高级事务函数,该函数现在接受一个 abort signal 作为参数。
为了知道中止何时发生,我正在为 signal 实例添加 "abort" 事件的事件监听器。该事件监听器将在每次触发 “abort” 事件时被调用,即当调用 controller.abort()
时。因此,当发生这种情况时,我可以使用 TransactionRollbackError 错误回滚整个事务,这与调用 tx.rollback()
并抛出相同错误是等价的。
现在,让我们用它来测试 Drizzle。
const db = drizzle(options)
const controller = new AbortController()
const transaction = makeCancelableTransaction(db)
await transaction(
async (tx) => {
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} - 100.00` })
.where(eq(users.name, 'Dan'))
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} + 100.00` })
.where(eq(users.name, 'Andrew'))
},
{ signal: controller.signal }
)
我正在使用名为 makeCancelableTransaction()
的实用程序函数,并使用名为 db 的实例来创建自定义的可中断的 transaction 。从现在开始,我可以在 Drizzle 中像往常一样使用我的自定义 transaction ,执行多个数据库操作,但我也可以为其提供一个中断信号,以便一次性取消所有操作。
中止错误处理
每次中止事件都会伴随着中止的原因。这使得您可以根据不同的异常原因采取不同的响应措施,从而使应用程序更加灵活。
中止原因是 controller.abort()
方法的可选参数。可以在任何 AbortSignal 实例的 reason 属性中访问终止原因。
const controller = new AbortController()
controller.signal.addEventListener('abort', () => {
console.log(controller.signal.reason) // "user cancellation"
})
// Provide a custom reason to this abort.
controller.abort('user cancellation')
reason 参数可以是任何 JavaScript 值,因此你可以传递字符串、错误,甚至对象。
结论
如果你在编写 JavaScript 库,其中取消或中止操作有意义,我强烈建议你不要寻找其他 API 了,因为 AbortController API 非常出色!如果你在构建应用程序,当需要取消请求、移除事件监听器、中止流或教给任何逻辑以可取消性时,可以很好地利用 abort controller。
关于本文
译者:@飘飘
作者:@Artem
原文:https://kettanaito.com/blog/dont-sleep-on-abort-controller
这期前端早读课
对你有帮助,帮” 赞 “一下,
期待下一期,帮” 在看” 一下 。