ECMAScript 双月报告:TC39 2023年3月会议提案进度汇总

简介: 在本次会议中,共有 9 个提案实现了 Stage 推进,其中阿里巴巴主导的 AsyncContext 提案进入到了 Stage 2。另外,有 4 个提案成功进入到 Stage 1,包括 Promise.withResolvers 以及 Class Method Param Decorators 等此前就广受关注的内置方法和语法提案。Stage 2 → Stage 3当一个提案进入 Stage 3 

在本次会议中,共有 9 个提案实现了 Stage 推进,其中阿里巴巴主导的 AsyncContext 提案进入到了 Stage 2。另外,有 4 个提案成功进入到 Stage 1,包括 Promise.withResolvers 以及 Class Method Param Decorators 等此前就广受关注的内置方法和语法提案。

Stage 2 → Stage 3

当一个提案进入 Stage 3 后,意味着浏览器、Node.js 等将会开始实现提案特性。在这个阶段的提案只有在发生重大问题时才会进行修改。

Explict Resource Management

提案链接:proposal-explicit-resource-management

此提案旨在为 JavaScript 中引入显式的资源管理能力,如对内存、I/O、文件描述符等资源的分配与显式释放。

在此前,JavaScript 中并不存在标准的资源管理能力,如以下的 Generator 函数:

function * g() {
  const handle = acquireFileHandle();
  try {
    ...
  }
  finally {
    handle.release(); // 释放资源
  }
}

const obj = g();
try {
  const r = obj.next();
  ...
}
finally {
  obj.return(); // 显式调用函数 g 中的 finally 代码块
}

在我们执行完毕生成器函数后,需要使用 obj.return() 来显式地调用 g 函数中的 finally 代码块,才能确保文件句柄被正确释放。

而通过此提案引入的 using 关键字,其可以被简化为如下的方式:

function * g() {
  using handle = acquireFileHandle(); // 定义与代码块关联的资源
}

{
  using obj = g(); // 显式声明资源
  const r = obj.next();
} // 自动调用释放逻辑

除此以外,NodeJs FileHandles 上的 handle.close() 方法,WHATWG Stream Readers 上的 reader.releaseLock() 均可以使用这种方式来简化资源的管理。

实际上,这里的“显式”对应的是此前如 WeakSet 与 WeakMap 这样,会由 JS 运行时作为垃圾回收进行的“隐式”工作。显式资源管理意味着,用户可以主动声明块级作用域内依赖的资源,通过 Symbol.disposable这样的命令式或 using 这样的声明式,然后在离开作用域时就能够由运行时自动地释放这些标记的资源。

(原 Import Assertion)Import Attributes

提案链接:proposal-import-attributes

此提案最初名为 Import Assertion,尝试在导入语句中新增断言语法,由开发者将导入模块断言为指定的类型,以此来提高 JS 引擎的处理效率,以派生自 Import Assertion 提案的 JSON Modules提案为例,其语法大致如下:

import json from "./foo.json" assert { type: "json" };
import("foo.json", { assert: { type: "json" } });

随着提案的推进,此提案的包含范围从“断言”进一步扩大,成为对整个导入语句的描述。同样以对 JSON Module 的描述,现在的语法是这样的:

import json from "./foo.json" with { type: "json" };
import("foo.json", { with: { type: "json" } });

同样的,对重导出语句、动态导入语句的语法也进行了调整:

export { val } from './foo.js' with { type: "javascript" };

import("foo.json", { with: { type: "json" } })

虽然语法和范畴进行了调整,但这一提案的主旨仍然是避免模块解析只能够依赖模块路径中的文件扩展名,从而进一步提升安全性。

实际上,除了 Import Attributes 提案以外,TC39 中还存在一个尝试为导入语句附加额外信息的提案 proposal-import-reflection(原 Evaluator Attributes ,目前位于 Stage 2),其为 import 语句支持了使用 as 关键字来声明导入反射属性(元数据)的能力,如:

import x from "" as "";

不同于 Import Attributes 实际上不会影响模块的解析行为,Import Reflection 中的元数据附加会改变 import 语句的对于目标模块的执行方式,以此提案的主要驱动场景之一为例, 为 WebAssembly 模块指定额外的类型,如实例导入(WebAssembly.Instance)与模块导入(WebAssembly.Module)。

import FooModule from "./foo.wasm" as "wasm-module";
FooModule instanceof WebAssembly.Module; // true

// WASI 是适用于 WebAssembly 的模块化系统调用规范
import { WASI } from 'wasi';
const wasi = new WASI({ args, env, preopens });

// 实例化 WebAssembly 模块,并与 WASI 实现链接
const fooInstance = await WebAssembly.instantiate(FooModule, {
  wasi_snapshot_preview1: wasi.wasiImport
});

// 执行
wasi.start(fooInstance);

以上示例使用了 wasm-module 作为反射信息,以改变对一个已编译完毕(但尚未链接)的 WebAssembly 模块对象的导入行为。

Stage 1 → Stage 2

当一个提案进入 Stage 2,意味着已经完成了特性细节的草稿设计,可能被用于实验性验证等早期实现。

Async Context

提案链接:proposal-async-context

在 JavaScript 异步上下文追踪对于浏览器、Node.js等运行时,应用框架、应用监控程序等来说一直是一个难以攻克的难题。对于同步执行的任务,React 通过 React Context追踪任务上下文,不过在引入异步回调、Promise、async/await 时即会失效。而 Angular 则选择了通过 Zone.js 实现一定程度上的异步上下文追踪能力。

近年,Node.js 通过 AsyncLocalStorage 等尝试,提供了基础的异步任务追踪的能力。但是对于普通开发者来说,缺少标准化方案还是难以让这个特性在如浏览器、Deno 等运行时上获得支持。

这个提案提议了一个能够将任意 JavaScript 值通过逻辑连接的同步、异步操作,传播到逻辑连接的异步操作的执行上下文的存储 AsyncContext

class AsyncContext {
  // 快照当前执行上下文中所有 AsyncContext 实例的值,并返回一个函数。
  // 当这个函数执行时,会将 AsyncContext 状态快照恢复为执行上下文的全局状态。
  static wrap(fn: (...args: any[]) => R): (...args: any[]) => R;

  // 立刻执行 fn,并在 fn 执行期间将 value 设置为当前 
  // AsyncContext 实例的值。这个值会在 fn 过程中发起的异步操作中被
  // 快照(相当于 wrap)。
  run(value: T, fn: () => R): R;

  // 获取当前 AsyncContext 实例的值。
  get(): T;
}

通过 AsyncContext即可实现在异步逻辑调用链上获得类似于 ReactContext 等同步调用上下文访问的能力:

// Framework listener
doc.addEventListener('click', () => {
  timer.run(Date.now(), async () => {
    // User code
    const f = await fetch(dataUrl);
    // 不需要额外传递时间戳参数
    patch(dom, await f.json());
  });
});
// Some framework code
const timer = new AsyncContext();
function patch(dom, data) {
  // 异步任务链中间节点不需要关心额外的参数传递
  doLotsOfWork(dom, data, update);
}
function update(dom, html) {
  // 通过 AsyncContext 获取异步任务链的开始时间
  log(Date.now() - timer.get());
  dom.innerHTML = html;
}

Float16Array

提案链接:proposal-float16array

JavaScript 中提供了专用于存储浮点数值的 Float32Array 与 Float64Array,它们分别对应 32 位和 64 位的浮点数,即单精度和双精度浮点数。由于它们的底层是字符串,这两个数组类型在存储/处理浮点数时能获得更高的效率与更少的内存占用。

然而,在某些场景下,我们可能会希望使用半精度浮点数,即 Float16Array,虽然它的表示范围与精度都要低于单精度浮点数 Float32Array,但是能够进一步提高运行效率和减少内存占用。如在神经网络、WebGL 中,由于我们不需要高精度的计算,性能瓶颈往往在需要存储和处理大量数据上,此时使用半精度浮点数,牺牲的精度换来大幅的性能提升是非常划算的。

除了引入 Floathe6Array 以外,此提案也会对应地添加 DataView.getFloat16 与 DataView.setFloat16 等方法补充。

Iterator.range

提案链接:proposal-iterator.range

range 是编程语言中非常常见的一个内置 API,用于快速生成一个数字类型数组,你能够指定其初始值、结束值以及其中的步长,如在 Python 中,range(1, 100, 2) 会生成 1-99 的奇数组成的数组。

这个 API 此前在 JavaScript 中是不存在的,因此此提案提出引入 Iterator.range 方法来实现类似的能力,需要注意的是它的返回结果是一个迭代器而非数组:

[...Iterator.range(1, 100, 2)]; // 1-99 的奇数

目前在 CoreJs 中已经提供了此方法的 Polyfill,参考 esnext.iterator.range

Stage 0 → Stage 1

所有 ECMAScript 提案都需要论证所提特性的价值、解决方案可行性。当提案进入 Stage 1 意味着提案的价值与设计方案正式被 TC39 接收,并开始标准化流程。

Await Dictionary of Promises

提案链接:proposal-await-dictionary

在日常开发中,一个相当常见的场景是将一组 Promise 的返回值存储到对象属性:

const obj = {
  shape: await getShape(),
  color: await getColor(),
  mass: await getMass(),
};

但这么写会导致异步操作实际上是串行而非并行执行。你可能会想到使用 Promise.all,但它是基于数组索引来进行赋值的,你可能一不小心就进行了错误地赋值:

// 实际上应当是 shape, color, mass
const [color, shape, mass] = await Promise.all([
  getShape(),
  getColor(),
  getMass(),
]);

此提案提出引入 Promise.ownProperties 与 Promise.fromEntries 两个内置方法,来实现对象版本的 Promise.all

const {
  shape,
  color,
  mass,
} = await Promise.ownProperties({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
});

const {
  shape,
  color,
  mass,
} = await Promise.fromEntries(Object.entries({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
}));

社区也有类似的实现,如 p-props的写法是这样的:

import pProps from 'p-props';

const obj = {
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
};

console.log(await pProps(obj));

Class Method Param Decorator

提案链接:proposal-class-method-parameter-decorators

目前位于 Stage 3 的新版的 ECMAScript 装饰器相比之前版本的装饰器缺少了类方法参数的装饰器支持,而这是旧版装饰器中相当重要的一个能力,它能够实现构造函数参数与方法参数中的依赖注入,或是方法参数的校验。

以 NestJs 为例,类方法参数装饰器在旧版的使用方式大致是这样的:

@Controller()
export class UserController {
  constructor(@Inject() prisma: PrismaService) {}

  @Get(':id')
	async queryOneUser(@Param('id') id: string) { }
}

在新版的装饰器提案中,类方法参数装饰器的类型定义如下:

type ParameterDecoratorContext = {
  kind: "parameter";
  name: string | undefined;
  index: number;
  rest: boolean;
  function: {
    kind: "class" | "method" | "setter";
    name: string | symbol | undefined;
    static?: boolean;
    private?: boolean;
  };
  metadata: object;
  addInitializer(initializer: () => void): void;
}

一个比较独特的字段是 rest 属性,它能够直接标识出当前被装饰的参数是否是 rest 参数类型。

Promise.withResolvers

提案链接:proposal-promise-with-resolvers

在构造一个 Promise 时,我们可以调用其内部函数的 resolve 与 reject 方法来更改这个 Promise 的状态。而一个常见的用法是,在其内部执行其它的异步操作,并根据操作结果来决定当前 Promise 的状态:

new Promise((res, rej) => {
	listener.on('end', (err, data) => {
    err ? rej(err) : res(data);
  })
})

但这一方式的限制是,我们必须将其它操作也放置在 Promise 的构造函数内部,也就是说,你无法在其外部去修改 Promise 的状态。

你可能会想到,将 resolve 与 reject 函数保存到外部,就像这样:

let resolve;
let reject;
const myPromise = new Promise((resolve_, reject_) => {
  resolve = resolve_;
  reject = reject_;
})

这种方式能很好地实现将 resolve 与 reject 方法暴露给外部控制的效果,但并不太优雅。而此提案则提出了更好的解决方式:为 Promise 新增一个静态方法 withResolvers,其调用结果会返回一个 Promise 实例,以及用于控制其的 resolve / reject 方法:

const { promise, resolve, reject } = Promise.withResolvers();

Time Zone Canonicalization

提案链接:proposal-canonical-tz

ECMAScript 中的时区名来自于 Time Zone Database,其中的数据会定期进行更新,需要开发者更新相关的时区命名。而此提案则希望在时区发生更新的情况下提供规范化的处理方式,目前其提出了包括简化时区标识符、更新 V8 与 WebKit 中过时的规范命名等措施,以及提出引入 Temporal.TimeZone.prototype.equals 方法来帮助判断两个时区标识是否指向同一个时区:

Temporal.TimeZone.from('Asia/Calcutta').equals('Asia/Kolkata');
// => true

总结

对 ECMAScript 有新想法、新需求?快来了解 TC39 是如何工作的( https://yuque.antfin-inc.com/esnext/how-we-work/fgqi7o),或者直接与 @昭朗 联系,加入 ESNext 研讨会小组(钉钉群号23188462)与我们讨论。

相关文章
|
JavaScript
cnpm 的安装与使用
本文介绍了npm和cnpm的概念、安装nodejs的步骤,以及cnpm的安装和使用方法,提供了通过配置npm使用中国镜像源来加速包下载的替代方案,并说明了如何恢复npm默认仓库地址。
cnpm 的安装与使用
|
7月前
|
网络协议 算法 安全
Go语言的网络编程与TCP_UDP
Go语言由Google开发,旨在简单、高效和可扩展。本文深入探讨Go语言的网络编程,涵盖TCP/UDP的基本概念、核心算法(如滑动窗口、流量控制等)、最佳实践及应用场景。通过代码示例展示了TCP和UDP的实现,并讨论了其在HTTP、DNS等协议中的应用。最后,总结了Go语言网络编程的未来发展趋势与挑战,推荐了相关工具和资源。
163 5
|
6月前
|
设计模式 网络协议 Java
04.里式替换原则介绍
里式替换原则(LSP)是面向对象设计的重要原则之一,确保子类可以无缝替换父类而不破坏程序功能。本文详细介绍了LSP的定义、背景、理解方法及应用场景,通过电商支付和鸟类飞行案例展示了如何遵循LSP,并分析了其优缺点。LSP强调子类应保持父类的行为一致性,有助于提高代码的可扩展性、可维护性和可重用性,但也可能导致过度设计。最后,对比了LSP与多态的区别,明确了LSP作为设计原则的重要性。
207 4
|
11月前
|
容灾 关系型数据库 数据库
阿里云RDS服务巴黎奥运会赛事系统,助力云上奥运稳定运行
2024年巴黎奥运会,阿里云作为官方云服务合作伙伴,提供了稳定的技术支持。云数据库RDS通过备份恢复、实时监控、容灾切换等产品能力,确保了赛事系统的平稳运行。
 阿里云RDS服务巴黎奥运会赛事系统,助力云上奥运稳定运行
|
缓存 前端开发
css内部样式和外部样式的性能比较和使用规范
CSS 的内部样式和外部样式各有优缺点,适用于不同场景。
|
11月前
|
机器学习/深度学习 人工智能 自然语言处理
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
2730 1
|
11月前
|
机器学习/深度学习 数据采集 人工智能
深度学习的魔法:用神经网络识别手写数字
本文将引导读者了解如何使用深度学习技术,特别是卷积神经网络(CNN)来识别手写数字。我们将从基础理论出发,逐步深入到实际操作,包括数据的预处理、模型的构建和训练,以及结果的评估。通过本文,读者不仅能掌握使用深度学习进行图像识别的技能,还能理解其背后的原理。让我们一同揭开深度学习的神秘面纱,探索其在图像处理领域的无限可能。
|
11月前
|
算法 网络性能优化
Audio Over IP的PTP时钟初探
Audio Over IP的PTP时钟初探
162 0
|
Docker 容器
GitLab Runner注册大揭秘:高效CI/CD的入门指南
GitLab Runner注册大揭秘:高效CI/CD的入门指南
462 0
GitLab Runner注册大揭秘:高效CI/CD的入门指南
|
人工智能 自然语言处理 算法
大模型+蒙特卡洛树搜索,一招让LLaMa-3 8B奥数水平直逼GPT-4
【6月更文挑战第25天】 - 复旦大学和上海AI Lab的研究者提出这一算法,用于增强大型语言模型在复杂数学推理任务中的能力,解决现有模型推理准确性问题。 - **MCTSr**流程包括初始化、选择、自细化、自评估、反向传播和UCT更新,通过多轮迭代提升答案质量。 - 实验显示,该算法在**GSM8K**、**GSM Hard**、**MATH**和**Olympiad-level**数据集上表现出色,尤其在多次迭代后。 - 尽管计算成本高且不适用于所有问题类型,但研究揭示了强化LLMs推理能力的新途径,对未来的AI应用具有指导意义。
381 8