Nodejs开发进阶R-AbortController

在本文中,笔者想要来讨论一下笔者认为在nodejs和JS程序体系中,一个不太起眼但有点意思的技术:AbortController。

概述

什么是AbortController? 笔者理解,这是一种基于“信号”模式的异步执行退出机制,AbortController原意就是“退出控制器”。在一些技术文章和术语中,AbortController也称为AbortSignal(退出信号),这个单词可能更接近于这个技术的内涵和本意。就是在一个异步执行过程中,要进行退出,也是通过事件监听和处理的模式实现的(后面的代码会看到),非常符合JavaScript运行的机制。

我们都已经了解,JavaScript语言的一大特征就是程序和方法是可以异步执行的。在一些运行时间比较长的操作中,我们希望引入一种主动控制机制,来中断或者提前结束异步方法的执行,从而增强异步编程的灵活性和可控制性。这时就可以并且应当使用AbortContorller。

具体而言,比如网络应用编程中的网络请求,就是一个非常典型的应用场景。当发起网络请求后,由于种种原因,我们并不能预期服务器何时可以进行响应或者可能会出现错误。这时候,基于性能和可用性的考量,我们一般会设置一个请求超时的机制,能够从主动请求中退出,而非无限期的等待;再有,就是下载大量的数据的时候,可能用户基于其他考虑,希望主动的中断和结束下载过程。这两种场景,都可以方便的使用AbortController处理,只不过前者可以结合定时器setTimeOut触发,而后者基于操作事件触发,而在退出机制上,其实实际是一样的。

我们下面借助一个fetch执行的示例程序来了解一下这个方法的具体应用方式。

fetch示例

下面是一个使用AbortController,进行nodejs fetch请求的超时控制的示例代码:

 

js

复制代码

// 创建控制器 const ac = new AbortController(); // 获取控制器信号对象 const { signal } = controller; // 可选取消回调 signal.addEventListener('abort', () => console.log('Signal Aborted!',signal.reason), { once: true }); // 或者 signal.onabort => console.log('Signal Aborted!',signal.reason); // fetch执行 fetch("https://google.com", { signal }) .then( res=> res.JSON()) .then( data => console) .catch(err => { if (err.name === 'AbortError') { console.log('Request was aborted', err.message); } }); // 超时取消请求 5s setTimeout(()=>{ controller.abort("Time Out"); },5000);

这段代码的操作执行逻辑和要点是:

  • 使用前需要先创建一个AbortController实例
  • 该实例会包括一个signal对象,处理退出信号相关的操作
  • 执行异步方法时,需要将这个signal对象作为参数传入
  • 可以调用被动或主动调用abort()方法实施退出
  • 如果设置了退出信号,则在退出时,fetch会抛出错误,错误名称为 AbortError
  • 可选abort(reson)参数和为signal对象设置abort回调方法,此参数会注入signal.reason属性
  • abort回调会先触发,然后抛出AbortError

在理解了其基本工作方式时候,我们来看一看nodejs中,相关的类和方法-AbortController。

AbortController类和方法

在nodejs中,AbortController是全局类,nodejs程序中无需引用就可以直接使用。查询相关技术文档,AbortController的结构和属性如下:

0a.png

也就是说,AbortController只有一个方法abort()和一个属性signal。而signal实际上是一个EventTarget。它的主要事件方法是“abort”,并具有aborted、onabort、reason等,都比较好理解。

笔者觉得AbortSignal的核心应该是throeIfAborted方法,通过它可以实现在收到退出信号时执行的机制,通常就是抛出一共AbortError错误(前例中的fetch error)。

除了singal类实例的属性和方法之外,模块还提供了几个静态方法,方便的构造常用的退出机制,或者构造多个退出信号的处理机制。例如,AbortSignal有一个静态方法timeout,可以用基于delay时间创建一个延时取消信号,所以其实前面的fetch代码,可以简化为下面这种形式:

 

js

复制代码

fetch("https://google.com", { signal: AbortSignal.timeout(3000) }) .then( res=> res.JSON()) .then( data => console) .catch(err => { if (err.name === 'AbortError') { console.log('Request was aborted:', err.message); } });

这个用法隐藏和简化了AbortController,在某些场景下更加简单直观。

AbortController和AbortSignal的关系

看到上面的AbortController类的设计,笔者对于它和AbortSignal之间的关系,其实是有一点疑问的。让我们来看看mistral是怎么理解的(原文有点啰嗦,笔者进行了简单的裁剪和编辑):

Q: 那为什么不直接为AbortSignal设计一个abort方法呢

A:

AbortSignal 没有直接提供 abort() 方法的原因是设计上的考虑。因为AbortSignal在设计上的作用是响应异步操作的取消,而AbortController是用于控制取消的对象(拥有和使用abort()方法)。这个设计的好处是可以更好地分离关注点,使得代码更加模块化、可维护和可扩展。

另外,一个 AbortSignal 可以被多个异步操作共享,而一个 AbortController只能控制一个AbortSignal。将 abort() 方法放在 AbortController 中还可以避免在多个异步操作之间共享一个 AbortSignal 时出现的潜在问题。

这个回答还是比较有说服力的,笔者原本只是隐隐约约的感觉到,这个回答确定了原来的想法。但第二点笔者还是有一点疑问,因为没有看到有说服力的示例,如果担心由于共享AbortSignal造成的潜在状态干涉和调试困难的问题,那完全可以使用多个信号实例。这个问题,可能需要等待到笔者有实际的多个退出控制的开发和操作经验后,才可能有比较确定的认知。

其他场景

我们从前面的代码可以看到,signal是作为参数选项(通常在选项参数中提供一个signal属性),被注入到异步方法执行的过程当中的,然后在执行过程中,接收到取消事件,就会触发一个取消错误抛出。也就是说,在那些方法的实现中,需要考虑和实现这个机制。nodejs中,有很多内置的对象和操作,已经实现了这个机制。它们包括:

  • Fetch:

在Fetch方法API中,支持通过传入 AbortSignal 在需要时中止 HTTP 请求,基本形式为:

fetch(url, { signal })

  • 流(Streams)

在Stream模块中,支持通过 AbortSignal 中止流管道操作,基本形式为:

stream.pipeline(...streams, { signal })

  • 文件系统

对于fs模块,支持使用 AbortSignal 取消文件读写操作,基本形式为:

fs.readFile(path, { signal })

fs.read(fd, buffer, ..., { signal })

fs.write(fd, buffer, ..., { signal })

  • 定时器

对于定时器方法,支持通过 AbortSignal 取消延时调用和周期调用,基本形式为:

setTimeout(fn, delay, { signal }) setInterval(fn, delay, { signal })

  • 事件监听

在EventEmiiter模块中,支持使用 AbortSignal 自动取消事件监听器,基本形式为:

emitter.once(eventName, { signal })

emitter.on(eventName, { signal })

  • Web Streams API

和StreamAPI类似,在Web Stream API中支持通过 AbortSignal 取消转换流的管道操作,基本形式为:

stream.pipeThrough({ signal }, ...transforms)

  • WebSocket

WebSocket对象支持使用AbortSignal中止连接,基本形式为:

const ws = new WebSocket(url, { signal })

当然,在了解了这个基本原理之后,我们有可以使用类似的方式,在自己的应用或者API中实现和集成AbortSingal,以提供灵活的操作控制能力。

Nodejs 22增强

在最新的nodejs 22更新中,还提到了对AbortSignal的增强,原文是这样描述的:

Improve performance of AbortSignal creation

This release enhances the efficiency of creating AbortSignal instances, leading to significantly improved performance in fetch and the test runner.

简单说就是nodejs 22版本中,改进了AbortSignal创建的性能,导致对于fetch和test runner有很明显的性能改善影响。但没有提到其基本的作用原理和实现细节。

小结

虽然在nodejs庞大的技术体系中,AbortController并不是很起眼,但笔者认为,它却代表了一种很好的程序执行和过程管理的范式,就是一种一致性和优雅的退出控制机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值