JavaScript 新特性:显式资源管理

发布于 2025 年 5 月 9 日 · 标签:ECMAScript

_显式资源管理_提案引入了一种确定性的方法来显式地管理资源生命周期,例如文件句柄、网络连接等。 该提案为语言带来了以下补充:usingawait using 声明,它们在资源超出作用域时自动调用 dispose 方法;用于清理操作的 [Symbol.dispose]()[Symbol.asyncDispose]() 符号;两个新的全局对象 DisposableStackAsyncDisposableStack 作为聚合可释放资源的容器;以及 SuppressedError 作为一种新的错误类型(包含最近抛出的错误以及被抑制的错误),用于解决在资源释放期间发生错误,并可能掩盖主体中或另一个资源的释放中抛出的现有错误的情况。 这些新增功能使开发人员能够通过提供对资源释放的精细控制来编写更健壮、高性能和可维护的代码。

usingawait using 声明 #

显式资源管理提案的核心在于 usingawait using 声明。 using 声明专为同步资源设计,确保在声明它的作用域退出时调用可释放资源的 [Symbol.dispose]() 方法。 对于异步资源,await using 声明的工作方式类似,但确保调用 [Symbol.asyncDispose]() 方法并等待此调用的结果,从而允许进行异步清理操作。 这种区别使开发人员能够可靠地管理同步和异步资源,防止泄漏并提高整体代码质量。 usingawait using 关键字可以在花括号 {} 内使用(例如块、for 循环和函数体),不能在顶层使用。

例如,使用 ReadableStreamDefaultReader([https://v8.dev/features/https:/developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader](https://v8.dev/features/https:/developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) 时,调用 reader.releaseLock() 来解锁流并允许在其他地方使用它至关重要。 但是,错误处理引入了一个常见问题:如果在读取过程中发生错误,并且您忘记在错误传播之前调用 releaseLock(),则流将保持锁定状态。 让我们从一个简单的例子开始:

let responsePromise = null;async function readFile(url) {   if (!responsePromise) {    // Only fetch if we don't have a promise yet    responsePromise = fetch(url);  }  const response = await responsePromise;  if (!response.ok) {   throw new Error(`HTTP error! status: ${response.status}`);  }  const processedData = await processData(response);  // Do something with processedData  ... }async function processData(response) {  const reader = response.body.getReader();  let done = false;  let value;  let processedData;    while (!done) {    ({ done, value } = await reader.read());    if (value) {      // Process data and save the result in processedData      ...      // An error is thrown here!    }  }    // Because the error is thrown before this line, the stream remains locked.  reader.releaseLock();   return processedData; } readFile('https://example.com/largefile.dat');

因此,开发人员在使用流时拥有 try...finally 块并将 reader.releaseLock() 放入 finally 中至关重要。 这种模式确保始终调用 reader.releaseLock()

async function processData(response) {  const reader = response.body.getReader();  let done = false;  let value;  let processedData;    try {    while (!done) {      ({ done, value } = await reader.read());      if (value) {        // Process data and save the result in processedData        ...        // An error is thrown here!      }    }  } finally {    // The reader's lock on the stream will be always released.    reader.releaseLock();  }  return processedData; } readFile('https://example.com/largefile.dat');

编写此代码的另一种方法是创建一个可释放对象 readerResource,它具有 reader (response.body.getReader()) 和调用 this.reader.releaseLock()[Symbol.dispose]() 方法。 using 声明确保在代码块退出时调用 readerResource[Symbol.dispose](),并且不再需要记住调用 releaseLock,因为 using 声明会处理它。 未来可能会将 [Symbol.dispose][Symbol.asyncDispose] 集成到像流这样的 Web API 中,因此开发人员不必编写手动包装器对象。

 async function processData(response) {  const reader = response.body.getReader();  let done = false;  let value;  // Wrap the reader in a disposable resource  using readerResource = {    reader: response.body.getReader(),    [Symbol.dispose]() {      this.reader.releaseLock();    },  };  const { reader } = readerResource;  let done = false;  let value;  let processedData;  while (!done) {    ({ done, value } = await reader.read());    if (value) {      // Process data and save the result in processedData      ...      // An error is thrown here!    }  }  return processedData; } // readerResource[Symbol.dispose]() is called automatically. readFile('https://example.com/largefile.dat');

DisposableStackAsyncDisposableStack #

为了进一步促进管理多个可释放资源,该提案引入了 DisposableStackAsyncDisposableStack。 这些基于堆栈的结构允许开发人员以协调的方式对多个资源进行分组和释放。 资源被添加到堆栈中,当堆栈被同步或异步释放时,资源按照它们被添加的相反顺序释放,从而确保正确处理它们之间的任何依赖关系。 这简化了处理涉及多个相关资源的复杂场景时的清理过程。 两种结构都提供诸如 use()adopt()defer() 之类的方法来添加资源或释放操作,以及 dispose()asyncDispose() 方法来触发清理。 DisposableStackAsyncDisposableStack 分别具有 [Symbol.dispose]()[Symbol.asyncDispose](),因此它们可以与 usingawait using 关键字一起使用。 它们提供了一种强大的方法来管理定义范围内的多个资源的释放。

让我们看一下每种方法,并看一个例子:

use(value) 将资源添加到堆栈顶部。

{  const readerResource = {    reader: response.body.getReader(),    [Symbol.dispose]() {      this.reader.releaseLock();      console.log('Reader lock released.');    },  };  using stack = new DisposableStack();  stack.use(readerResource);}// Reader lock released.

adopt(value, onDispose) 将不可释放的资源和释放回调添加到堆栈顶部。

{  using stack = new DisposableStack();  stack.adopt(   response.body.getReader(), reader = > {    reader.releaseLock();    console.log('Reader lock released.');   });}// Reader lock released.

defer(onDispose) 将释放回调添加到堆栈顶部。 它对于添加没有关联资源的清理操作很有用。

{  using stack = new DisposableStack();  stack.defer(() => console.log("done."));}// done.

move() 将此堆栈中当前的所有资源移动到新的 DisposableStack 中。 如果您需要将资源的所有权转移到代码的另一部分,这会很有用。

{  using stack = new DisposableStack();  stack.adopt(   response.body.getReader(), reader = > {    reader.releaseLock();    console.log('Reader lock released.');   });  using newStack = stack.move();}// Here just the newStack exists and the resource inside it will be disposed.// Reader lock released.

DisposableStack 中的 dispose()AsyncDisposableStack 中的 asyncDispose() 释放此对象中的资源。

{  const readerResource = {    reader: response.body.getReader(),    [Symbol.dispose]() {      this.reader.releaseLock();      console.log('Reader lock released.');    },  };  let stack = new DisposableStack();  stack.use(readerResource);  stack.dispose();}// Reader lock released.

可用性 #

显式资源管理已在 Chromium 134 和 V8 v13.8 中发布。

显式资源管理支持 #

关于此功能支持列表

由 Rezvan Mahdavi Hezaveh 发布。

Branding Terms Privacy Twitter 在 GitHub 上编辑此页面

除非另有说明,否则 V8 项目中的任何代码示例均已获得 V8 的 BSD 风格许可 许可。 此页面上的其他内容已获得 Creative Commons Attribution 3.0 许可 许可。 有关详细信息,请参阅 我们的网站政策