Redian新闻
>
ECMAScript 双月报告:Array.fromAsync 进入 Stage 3

ECMAScript 双月报告:Array.fromAsync 进入 Stage 3

科技

作者:穹心
审校:昭朗

在本次 TC39 会议中,或许是由于在亚洲时区(东京时间)举办的原因,整体提交的提案数量较少,也仅有三个提案取得了阶段性进展。另外,本次会议中没有提案进入到 Stage 4 阶段。

Stage 2 → Stage 3

提案从 Stage 2 进入到 Stage 3 有以下几个门槛:

  1. 撰写了包含提案所有内容的标准文本,并有指定的 TC39 成员审阅并签署了同意意见;
  2. ECMAScript 编辑签署了同意意见。

Array.fromAsync

提案链接:proposal-array-from-async[1]

在 JavaScript 中,Array.from 方法用于从一个类数组或可迭代对象(Iterable,即部署了 [Symbol.iterator] 接口的对象)创建一个新的数组。

在从可迭代对象创建数组时,其实际上等价于以下的代码:

const arr = [];
for (const v of iterable) {
  arr.push(v);
}

// 等价于
const arr = Array.from(iterable);

然而还有一种常见的场景是,从异步迭代对象(Async Iteratable,即部署了 [Symbol.asyncIterator] 接口的对象)类型创建数组,此时常见的方式是使用 for await of 语法:

const arr = [];
for await (const v of asyncIterable) {
  arr.push(v);
}

而为了在语言层面支持这一能力,此提案引入了 Array.fromAsync 方法,来从异步迭代对象生成数组。

此方法会从迭代对象(包括 Iteratable 与 Async Iteratable)立即生成一个 Promise ,其成功 resolve 将返回一个数组:

function sleep() {
  return new Promise((res, rej) => {
    setTimeout(res, 1000);
  });
}

Array.fromAsync = async (source) => {
  const arr = [];

  for await (const entry of source) {
    arr.push(entry);
  }

  return arr;
};

const arr = [1, 2, 3, 4];

// 异步迭代
async functionasyncGen() {
  for (const i of arr) {
    await sleep();
    yield i;
  }
}

// 同步迭代
functionsyncGen() {
  for (const i of arr) {
    yield i;
  }
}

(async () => {
  console.log(Array.fromAsync(syncGen()));
  console.log(Array.fromAsync(asyncGen()));
})();

以上调用均会立刻输出两个 Promise :

Promise { <pending> }
Promise { <pending> }

而如果我们 await 这两个 Promise,那么对 asyncGen() 的调用将会创建一个异步迭代器(Async Iterator),然后依次等待每一个内部的 Promise resolve,再将其值添加进结果数组,最后返回这个数组:

// 来自于 syncGen() 的调用会立刻返回
[ 1, 2, 3, 4 ]
// 来自于 asyncGen() 的调用等待 4s 后才打印
[ 1, 2, 3, 4 ]

而如果同步可迭代对象也返回了 Promise ,那么 fromAsync 同样会顺序地依次等待每一个 Promise resolve:

// 异步迭代
async functionasyncGen() {
  for (const i of arr) {
    await sleep();
    yield i;
  }
}

// 生成 Promise 的同步迭代
functionsyncGenWithPromise() {
  for (const i of arr) {
    yield sleep().then(() => i);
  }
}
(async () => {
  console.log(await Array.fromAsync(syncGenWithPromise()));
  console.log(await Array.fromAsync(asyncGen()));
})();
// 等待 4s 后打印
[ 1, 2, 3, 4 ]
// 再等待 4s 后打印
[ 1, 2, 3, 4 ]

但如果使用 Array.from 方法来迭代返回 Promise 的同步可迭代对象,实际上其中的各个 Promise 会是彼此独立的,即无需等待上一个 Promise settle :

// 生成 Promise 的同步迭代
functionsyncGenWithPromise() {
  for (const i of arr) {
    // 越往后,越快 resolve
    yield sleep(2000 - i * 100).then(() => {
      console.log(`${i} resolved`);
      return i;
    });
  }
}
(async () => {
  console.log(await Promise.all(Array.from(syncGenWithPromise())));
})();
4 resolved
3 resolved
2 resolved
1 resolved
[ 1, 2, 3, 4 ]

最后,你可能会想到与 Array.fromAsync 有些相似的 Promise.all 方法,但Promise.all将并行地等待内部所有的 Promise resolve,然后一次性返回所有结果:

// 生成 Promise 的同步迭代
functionsyncGenWithPromise() {
  for (const i of arr) {
    yield sleep().then(() => i);
  }
}

(async () => {
  console.log(await Promise.all(Array.from(syncGenWithPromise())));
})();
// 只需等待 1s
[ 1, 2, 3, 4 ]

Stage 1 → Stage 2

从 Stage 1 进入到 Stage 2 需要完成撰写包含提案所有内容的标准文本的初稿。

Well-formed Unicode strings

提案链接:proposal-is-usv-string[2]

ECMAScript 字符串都是 UTF-16 编码的字符串。在 Web API 中,我们可以发现有些 API (如 URL、URLSearchParams 等等系列 API)都声明了需要 USVString 作为参数。什么是 USVString?USV 代表 Unicode Scalar Value,即 Unicode 标量值。根据 Unicode 定义,Unicode 的码位(Code Point)可以分成几个类别,分别是图形码(Graphic),格式码(Format),控制码(Control),私有码(Private-Use),代理码(Surrogate),非字符码(Noncharacter),与保留码(Reserved)。而其中的代理码又分成了高位代理码与低位代码码,只有当一个高位代码码与一个低位代理码组合成一个代理码对,才是一个合法的 Unicode 字符。

目前,JavaScript 字符串并不限制这个字符串的值是否是合法的 Unicode 值,比如我们可以编码一个字符串只有高位代理码,而没有低位代理码等等。而如严格的 Web URL API 定义必须要求参数字符串是合法的 Unicode 标量值,因此我们需要有方法能够去区分一个字符串是否是合法的 Unicode 标量值。

这个提案提出为 ECMAScript 引入新的内置方法 String.prototype.isWellFormed,  用于检查这个字符串是否是一个合法的 Unicode 标量值:

'\ud800'.isWellFormed(); // => false
'\ud800\udc00'.isWellFormed(); // => true

另外此提案也提供了 String.prototype.toWellFormed 方法,来将普通字符串转换到一个格式正确的 USV 字符串。类似的,NodeJs 中也提供了 util.toUSVString 这样的方法来实现此功能。

Stage 0 → Stage 1

从 Stage 0 进入到 Stage 1 有以下门槛:

  1. 找到一个 TC39 成员作为 champion 负责这个提案的演进;
  2. 明确提案需要解决的问题与需求和大致的解决方案;
  3. 有问题、解决方案的例子;
  4. 对 API 形式、关键算法、语义、实现风险等有讨论、分析。Stage 1 的提案会有可预见的比较大的改动,以下列出的例子并不代表提案最终会是例子中的语法、语义。

Extractor Objects

提案链接:proposal-extractors[3]

提取器语法是 Scala 中用于快速提取实例属性的语法糖,在 Scala 中,我们可以通过 apply 方法定义类的实例化方法,通过 unapply 方法(即提取器)反转这个过程——从实例获得实例化时的入参。

如以下的 Scala 代码:

object UserId:

 // 生成一个 UserId 字符串
  def apply(name: String) = s"userId--$name"

 // 从 UserId 字符串获得生成时的 name
  def unapply(userId: String): Option[String] =
    val stringArray: Array[String] = userId.split("--")
    if stringArray.tail.nonEmpty then Some(stringArray.tail) else None

// 定义了 apply 方法后,才能通过这种方式进行实例化
val userId1 = UserId("小明")  // userId-小明

// 通过提取器获得其 name 
val UserId(name1) = userId1
println(name1)  // 小明

// 也可以直接应用于字符串,在无法提取时会返回一个 None 类型
val UserId(name2) = "userId-大明"
println(name2)  // 大明

而其提案即旨在为 ECMAScript 引入提取器语法,包括数组提取器与对象提取器两种使用形式,如以下 JavaScript 代码:

class Foo {
  constructor(foo, bar, baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

const foo = new Foo();

// 提取 foo bar
const Foo(arg1, arg2) = foo;
// 提取 foo baz
const Foo{foo, baz} = foo;

以上代码使用的是绑定模式语法(Binding Pattern),你也可以使用分配模式(Assignment Pattern),有点类似函数声明与函数表达式的区别:

Foo(arg1, arg2) = foo;
Foo{foo, baz} = foo;

而提取器语法也可以和 Pattern Matching[4] 提案协作,我们还是先看看 Scala 中这两种语法的组合:

userId1 match
  case UserId(name1) => println(name1)  // 小明
  case _ => println("提取用户 ID 失败")

而在 ECMAScript 中,结合提取器语法和模式匹配,我们能够实现在解构赋值的同时进行校验或是二次处理,如以下的例子:

// 确保值为 Instance 类型,即一个不包含时区信息的精确时间
const InstantExtractor = {
  // 通过部署 Symbol.matcher 接口实现自定义匹配
  [Symbol.matcher]: value =>
    value instanceof Temporal.Instant ? { matched: true, value: [value] } :
    value instanceof Date ? { matched: true, value: [Temporal.Instant.fromEpochMilliseconds(value.getTime())] } :
    typeof value === "string" ? { matched: true, value: [Temporal.Instant.from(value)] } :
    { matched: false };
  }
};

class Book {
  constructor({
    title,
    // 在解构出这个值的同时,对其进行格式转换
    createdAt: InstantExtractor(createdAt) = Temporal.Now.instant(),
    modifiedAt: InstantExtractor(modifiedAt) = createdAt
  }) {
    this.title = title;
    this.createdAt = createdAt;
    this.modifiedAt = modifiedAt;
  }
}

而这也是解构赋值自 ES6 加入 JavaScript 以来一个呼声强烈的功能——解构时的额外处理逻辑。通过解构赋值结合提取器,我们能够将值的读取、校验与处理合并在一处,确保在后续消费时可以直接使用。

总结

由贺师俊牵头,阿里巴巴前端标准化小组等多方参与组建的 JavaScript 中文兴趣小组(JSCIG,JavaScript Chinese Interest Group)在 GitHub 上开放讨论各种 ECMAScript 的问题,非常欢迎有兴趣的同学参与讨论:esdiscuss。

参考资料

[1]

proposal-array-from-async: https://github.com/tc39/proposal-array-from-async

[2]

proposal-is-usv-string: https://github.com/tc39/proposal-is-usv-string

[3]

proposal-extractors: https://github.com/tc39/proposal-extractors

[4]

Pattern Matching: https://github.com/tc39/proposal-pattern-matching



微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
China Bans Schools From Forcing E-Devices on StudentsSoy Sauce ‘Double Standards’ Stir Massive Controversy in ChinaIKEA x OBEGRÄNSAD联名!宜家22年最受瞩目系列开售!ECCO OFFROAD男士凉鞋,72%折扣,现价$63.2!@ ecco8/30 波士顿新闻汇总|市议会主席取消Ricardo Arroyo职务 Lynn男子在Revere撞上灯杆后死亡【Legends in Concert】怀旧经典金曲来袭【永恒的传奇演唱会】Direct from Las Vegas我的爱好之一是“狗仔”我的孩儿和孙儿-:)我在斯坦福当教练和评委 My Learnings from Coaching Stanford Entrepreneurs!Restaurants to Livestream From Kitchens to Ensure Food Safety英国黄金周大促!比斯特折上8折、Fresh全场75折、Selfridges低至3折!九大投行|Morgan Stanley 2023 Cross Risk Analyst Programme正在进行中!陌上花开909 readPassage(&stranger); life.partner = stranger使用 External Secrets Operator 安全管理 Kubernetes SecretsPrompt总结 | 从MLM预训任务到Prompt Learning原理解析与Zero-shot分类、NER简单实践祝允明《五云裘歌》卷Gender Gap in China’s Science Sector Narrowing, Report SuggestsScience:肌注疫苗接种不能诱导呼吸道局部的黏膜免疫,但疫苗突破性感染可以;Omicron感染诱导的抗体可及SARS病毒!Synchrony PayPal Cashback 信用卡【2022.9 更新:$150 targeted 开卡奖励】PromptCLUE:大规模多任务Prompt预训练中文开源模型CLUE社区最新神器!PromptCLUE:大规模多任务Prompt预训练中文开源模型平价买到高级感!IKEA全新 OBEGRÄNSAD系列,全系列都好看!Logitech Z506 Surround Sound Home Theater Speaker System博士毕业,玩赛车爱摄影,是Principle Architect,也是Project Leader,这位工程师凭啥?北非后花园丨摩洛哥 卡萨布兰卡+马拉喀什+瓦尔扎扎特+撒哈拉沙漠+非斯+舍夫沙万 9天7晚游 CMNCMN9最航运 | 中远海运Syncon Hub数字化供应链产品升级上线(海运、陆运、报关、仓配)!【JFO】逃离现实Escape From Reality Livehouse • 002,一年一度,再次出发!Key Highlights From China’s Newly Revised Women’s Protection Law同样是PM,Product Manager、Program Manager、Project Manager的薪资哪个更高?破纪录100度高温,Revere Beach洗海澡看沙雕My Journey From Concerned Parent to LGBT AllyPrivate Study Rooms Emerge as Refuge for ExamineesXi’an Confronts an Unusual Challenge: a Surfeit of Ancient Tombs南北两伯乐— 一锤定音,他选择了《梁祝》在美国183.精神专家,在哪找Zhengzhou Becomes First Big City to Scrap ‘Hukou’ Restrictions
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。