本文总结了 2019 年谷歌 I/O 大会上 Mathias 和 Sathya 提出来的 JavaScript 新规范功能。
本文从对线程认识的简要刷新、事件循环、关于事件循环的常见问题和错误认识三个不同层面进行说明,进一步挖掘了 Node 的核心。
正则表达式 lookbehind
正则表达式(RegEx 或正则表达式)在任何语言里都是强大的功能。在字符串中搜索复杂的模式时正则表达式就能大显身手了。之前 JavaScript 中的正则表达式实现已经非常齐全了,唯一缺少的就是 lookbehind。
Lookahead
首先我们来了解一下正则表达式中的 lookahead 是什么含义,JavaScript 已经支持 lookahead 了。正则表达式中的 lookahead 语法允许你在字符串中选出的模式具有以下属性:另一个已知模式正好紧靠这个模式或不与其相邻,或者在这个模式之后。例如在字符串“MangoJuice,VanillaShake,GrapeJuice”中,我们可以使用正向 lookahead 语法来查找旁边有 Juice 的单词,即 Mango 和 Grape。
有两种类型的 lookahead,分别是正向 lookahead 和负向或否定的 lookahead。
正向 lookahead
正向 lookahead 选出的模式具有以下属性:另一个已知模式位于选出模式之后。正向 lookahead 的语法如下。
上面的模式选出了大写或小写字母的单词,单词旁边都会有 Juice。不要把它和正则表达式中的捕获组混淆。Lookahead 和 Lookbehind 都是写在括号里的,但它们没有被捕获。下面看一个正向 lookahead 的实际例子。
const testString = "MangoJuice, VanillaShake, GrapeJuice";
const testRegExp = /[a-zA-Z]+(?=Juice)/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Mango", "Grape"]
复制代码
负向 lookahead
类似地,上面的例子中如果使用负向 lookahead,就是选出所有后面没有 Juice 的单词。负向 lookahead 的语法与正向 lookahead 的语法类似,但有一处不同。我们需要把=符号换成!符号。
上面的正向表达式模式会选出所有后面不跟着 Juice 的单词。但上面的模式会选出给定字符串中的所有单词,因为给定字符串中的所有单词都不以 Juice 结尾,因此我们需要更具体些的规则。
/(Mango|Vanilla|Grape)(?!Juice)/
复制代码
这种模式将选出 Mango、Vanilla 或 Grape 几个单词,它们后面没有跟着 Juice。来看具体的代码。
const testString = "MangoJuice, VanillaShake, GrapeJuice";
const testRegExp= /(Mango|Vanilla|Grape)(?!Juice)/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Vanilla"]
复制代码
Lookbehind
与 lookahead 类似,Lookbehind 也是正则表达式中的一种语法,用它选出的模式具有以下属性:字符串中的某个已知模式位于或不在它的前面。例如,在字符串“FrozenBananas,DriedApples,FrozenFish”中,我们可以使用正向 lookbehind 来找到前面有 Frozen 的单词,比比如 Bananas 和 Fish。
与 lookahead 类似,这里也有一个正向 lookbehind 和负向或否定 lookbehind。
正向 lookbehind
正向 lookbehind 选出的模式具有以下属性:另一个已知模式位于它的前面。正向 lookbehind 的语法如下。
lookbehind 的模式类似于 lookahead,但带有额外的<符号,表示在前。上面的模式会选出所有以 Frozen 开头的单词或者在前面有 Frozen 的单词。来看具体的操作。
const testString = "FrozenBananas, DriedApples, FrozenFish";
const testRegExp = /(?<=Frozen)[a-zA-Z]+/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Bananas", "Fish"]
复制代码
负向 lookbehind
负向 lookbehind 选出的模式具有以下属性:另一个已知模式不在它的前面。例如要选出“FrozenBananas,DriedApples,FrozenFish”字符串中前面没有 Frozen 的单词,我们将使用以下语法。
但上面的模式将选出字符串中的所有单词,因为所有单词前面都没有 Frozen(FrozenBannanas 这样的单词这里会被视为一整个单词),我们需要更具体一些。
/(?<!Frozen)(Bananas|Apples|Fish)/
复制代码
写在代码中:
const testString = "FrozenBananas, DriedApples, FrozenFish";
const testRegExp = /(?<!Frozen)(Bananas|Apples|Fish)/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Apples"]
复制代码
支持范围——TC39:阶段 4;Chrome:62+;Node:8.10.0+
类字段
类字段(class field)是一种新的语法,用来从类构造函数外部定义实例(对象)的属性。有两种类型的类字段,公共类字段和私有类字段。
公共类字段
之前我们必须在类构造函数中定义对象上的属性。这些属性是公共的,意味着可以在类(对象)的实例上访问它们。
class Dog {
constructor() {
this.name = 'Tommy';
}
}
复制代码
每当我们有一个扩展父类的类时,必须先从构造函数中调用 super,然后才能在子类上添加属性,如下所述。
class Animal {}
class Dog extends Animal {
constructor() {
super(); // call super before using `this` in constructor
this.sound = 'Woof! Woof!';
}
makeSound() {
console.log( this.sound );
}
}
// create instance
const tommy = new Dog();
tommy.makeSound(); // Woof! Woof!
复制代码
现在有了公共类字段语法,我们就可以在类的构造函数之外定义类字段,JavaScript 将隐式调用 super。
class Animal {}
class Dog extends Animal {
sound = 'Woof! Woof!'; // public class field
makeSound() {
console.log( this.sound );
}
}
// create instance
const tommy = new Dog();
tommy.makeSound(); // Woof! Woof!
复制代码
当 JavaScript 隐式调用 super 时,它会在实例化类时传递用户提供的所有参数(这是标准的 JavaScript 行为,与私有类字段无关)。因此,如果你的父构造函数需要定制参数,请确保手动调用 super。
class Animal {
constructor( ...args ) {
console.log( 'Animal args:', args );
}
}
class Dog extends Animal {
sound = 'Woof! Woof!'; // public class field
makeSound() {
console.log( this.sound );
}
}
// create instance
const tommy = new Dog( 'Tommy', 'Loves', 'Toys!' );
tommy.makeSound(); // Animal args: [ 'Tommy', 'Loves', 'Toys!' ]
复制代码
支持范围——TC39:阶段 3;Chrome:72+;Node:12+
私有类字段
众所周知,JavaScript 没有 public、private 和 protected 之类的属性修饰符。默认情况下,对象上的所有属性都是公共的,这意味着任何人都可以访问它们。想要定义一个对外界隐藏的属性,最接近的方法是使用 Symbol 这个属性名称。你可能会使用_前缀来表示应该是私有的属性,但它只是一种表示法,不能解决问题。
现在有了私有类字段,我们可以让类属性只能在类中访问,并防止它们反映在实例(对象)上。来看前面的一个例子,先看一个暴露的属性。
class Dog {
_sound = 'Woof! Woof!'; // this is private
makeSound() {
console.log( this._sound );
}
}
// create instance
const tommy = new Dog();
console.log( tommy._sound ); // Woof! Woof!
复制代码
添加_前缀并不能解决我们的问题。私有类字段的定义方式与定义公共类字段的方式相同,但我们不必添加下划线前缀,而是添加 #前缀。访问对象上的私有属性将导致 SyntaxError: Undefined private field。
class Dog {
#sound = 'Woof! Woof!'; // this is private
makeSound() {
console.log( this.#sound );
}
}
// create instance
const tommy = new Dog();
tommy.makeSound() // Woof! Woof!
//console.log( tommy.#sound ); // SyntaxError
复制代码
私有属性只能在定义它们的类中访问。因此在父类或子类内无法访问私有属性。
我们还可以使用未定义的值定义私有(和公共)属性。
class Dog {
#name;
constructor( name ) {
this.#name = name;
}
showName() {
console.log( this.#name );
}
}
// create instance
const tommy = new Dog( 'Tommy' );
tommy.showName(); // Tommy
复制代码
支持范围——TC39:阶段 3;Chrome:74+;Node:12+
string.matchAll
我们在 string 数据类型上有 match 原型方法,它根据给定的正则表达式或关键字返回字符串中的匹配模式。
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /([A-Z0-9]+)/g;
console.log( colors.match( matchColorRegExp ) );
// Output:
["EEE", "CCC", "FAFAFA", "F00", "000"]
复制代码
但这种方法不提供其他附加信息,例如字符串中每个匹配的索引。删除 g 标志后可以生成其他信息,但之后我们只能获得第一个匹配项。
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/;
console.log( colors.match( matchColorRegExp ) );
// Output: (result shortned for viewing purpose)
["#EEE", "EEE", index: 0, input: "<colors>"]
复制代码
最后,我们需要在正则表达式对象和语法上使用.exec 方法,这样写起来没那么复杂。我们需要使用 while 循环,直到 exec 返回 null 为止。但要注意,exec 不会返回迭代器。
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
// in strict mode,
// Uncaught ReferenceError: match is not defined
while( match = matchColorRegExp.exec( colors ) ) {
console.log( match );
}
// Output: (result shortned for viewing purpose)
["#EEE", "EEE", index: 0, input: "<colors>"]
["#CCC", "CCC", index: 6, input: "<colors>"]
["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"]
["#F00", "F00", index: 21, input: input: "<colors>"]
["#000", "000", index: 27, input: input: "<colors>"]
复制代码
为了解决这个问题,我们现在有了 matchAll 方法,它返回一个迭代器,并且这个迭代器的每次 next()调用都会连续返回匹配的项。
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
console.log( ...colors.matchAll( matchColorRegExp ) );
// Output: (result shortned for viewing purpose)
["#EEE", "EEE", index: 0, input: "<colors>"]
["#CCC", "CCC", index: 6, input: "<colors>"]
["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"]
["#F00", "F00", index: 21, input: input: "<colors>"]
["#000", "000", index: 27, input: input: "<colors>"]
复制代码
支持范围——TC39:阶段 4;Chrome:73+;Firefox:67+;Node:12+
命名捕获组
与其他语言相比,JavaScript 中捕获或捕获组的概念略有不同。每当我们在括号内放置一个正则表达式模式(lookahead 和 lookbehind 除外)时,它就会变成一个捕获组,所有匹配的模式都会反映在匹配的输出项中。
在前面的示例中,下面数组的第一项是正则表达式模式的完整匹配,而第二项是捕获组的匹配值。
["#EEE", "EEE", index: 0, input: "<colors>"]
复制代码
如果有多个捕获组,它们将连续显示在结果中。我们来看一个例子。
const str = "My name is John Doe.";
const matchRegExp = /My name is ([a-z]+) ([a-z]+)/i;
const result = str.match( matchRegExp );console.log( result );
// error, if result is null
console.log( { firstName: result[1], lastName: result[2] } );
// Output:
["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: undefined]
{firstName: "John", lastName: "Doe"}
复制代码
如上所示,输出的第一个元素是完整匹配的字符串,而第二个和第三个元素是捕获组的结果。
现在有了命名捕获组后,我们可以使用标签将单个捕获组结果保存在 groups 对象中。定义命名捕获组的语法是(?$pattern)。
const str = "My name is John Doe.";
const matchRegExp = /My name is (?<firstName>[a-z]+) (?<lastName>[a-z]+)/i;
const result = str.match( matchRegExp );
console.log( result );
console.log( result.groups );
// Output:
["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: {firstName: "John", lastName: "Doe"}]
{firstName: "John", lastName: "Doe"}
复制代码
命名捕获组也适用于 matchAll 方法。
支持范围——TC39:阶段 4;Chrome:64+;Node:10+
数字分隔符
我们写较大的整数或小数时,可读性一直是个大问题。例如,十亿写成数字是 1000000000,但你得数对零的个数才行,很多时候这都很让人头疼。
在较新版本的 JavaScript 中,我们可以使用_ 分隔符来分隔数字的各个部分,以增强可读性。
var billion = 1_000_000_000;
console.log( billion ); // 1000000000
复制代码
我们可以随意将_放在数字中,而 JavaScript 只会忽略它。这种方法适用于任何类型的数字,无论是整数、十进制、二进制、十六进制还是八进制数字都行。
console.log( 1_000_000_000.11 ); // 1000000000.11
console.log( 1_000_000_000.1_012 ); // 1000000000.1012
console.log( 0xFF_00_FF ); // 16711935
console.log( 0b1001_0011 ); // 147
console.log( 0o11_17 ); // 591
复制代码
支持范围——TC39:阶段 3;Chrome:75+;Node:12.5+
BigInt
JavaScript 中的数字是从 Number 函数(也是构造函数)创建的。一个数字可以可靠表示的最大值是(2⁵³ - 1),也就是 9007199254740991。也可以使用 Number.MAX_SAFE_INTEGER 生成这个数。
当我们写下数字时,JavaScript 用 Number 构造函数包装它,以生成一个在其原型上包含数字方法的对象。所有原始数据类型都会这样处理。参阅 Primitives vs Objects 这篇文章来理解这个理念。
那么如果我们继续加大这个数字会怎么样?
console.log( Number.MAX_SAFE_INTEGER ); // 9007199254740991
console.log( Number.MAX_SAFE_INTEGER + 10 ); // 9007199254741000
复制代码
上面代码中的最后一个日志输出返回了错误的结果。发生这种情况是因为 JavaScript 无法计算超过 Number.MAX_SAFE_INTEGER 值的数字。
现在有了 BigInt 就能解决这个问题了。BigInt 能让我们表示一个比 Number.MAX_SAFE_INTEGER 值更高的整数。与 Number 类似,BigInt 同时表现为一个函数和一个构造函数。加入 BigInt 后,JavaScript 有了新的 bigint 内置原始数据类型来表示大整数。
var large = BigInt( 9007199254740991 );
console.log( large ); // 9007199254740991n
console.log( typeof large ); // bigint
复制代码
JavaScript 会在整数的末尾添加 n 下标以表示 BigInt 整数形式。因此我们只需在整数的最末尾附加 n 就能写成 BigInt 了。
现在我们有了 BigInt,就可以安全地对具有 bigint 数据类型的大数字执行数学运算了。
var large = 9007199254740991n;
console.log( large + 10n ); // 9007199254741001n
复制代码
使用 number 数据类型的数字与使用 bigint 数据类型的数字不同,因为 bigint 只能表示整数。因此程序不允许 bigint 和 number 数据类型之间的算术运算。
BigInt 函数可以接受任何类型的数字,如整数、二进制、十六进制、八进制等。它会在内部统一转换为十进制。
BigInt 还支持数字分隔符。
var large = 9_007_199_254_741_001n;
console.log( large ); // 9007199254741001n
复制代码
支持范围——TC39:阶段 3;Chrome:67+;Firefox:68+;Node:10.4+
数组:flat 和 flatMap
数组对象上的 flat 和 flatMap 原型方法。
Array.flat
我们现在能在一个数组使用一个新的 flat(n)原型方法,它将数组展平到第 n 个深度并返回一个新数组。默认情况下 n 为 1。我们可以将 n 作为 Infinity 传递,以展平所有嵌套数组。
var nums = [1, [2, [3, [4, 5]]]];
console.log( nums.flat() ); // [1, 2, [3, [4,5]]]
console.log( nums.flat(2) ); // [1, 2, 3, [4,5]]
console.log( nums.flat(Infinity) ); // [1, 2, 3, 4, 5]
复制代码
支持范围——TC39:阶段 4;Chrome:69+;Firefox:62+;Node:12+
Array.flatMap
日常编程工作中有时可能会使用 map 变换数组,然后将其展平。例如计算一些整数的平方。
var nums = [1, 2, 3];
var squares = nums.map( n => [ n, n*n ] )
console.log( squares ); // [[1,1],[2,4],[3,9]]
console.log( squares.flat() ); // [1, 1, 2, 4, 3, 9]
复制代码
我们可以使用 flatMap 原型方法,用一个语法同时执行映射和展平。它只能将从回调函数返回的数组展平到 1 的深度。
var nums = [1, 2, 3];
var makeSquare = n => [ n, n*n ];
console.log( nums.flatMap( makeSquare ) ); // [1, 1, 2, 4, 3, 9]
复制代码
支持范围——TC39:阶段 4;Chrome:69+;Firefox:62+;Node:11+
对象:fromEntries
我们可以使用对象的 entries 静态方法提取对象的 key:value 对,该方法返回一个数组,其中每个元素都是一个数组,后者的第一项是 key,第二项是 value。
var obj = {x:1,y:2,z:3};
var objEntries = Object.entries(obj);
console.log(objEntries); // [[“x”,1],[“y”,2],[“z”,3]]
复制代码
我们现在可以在对象上使用 fromEntries 静态方法,它会将条目转换回对象。
var entries = [["x", 1],["y", 2],["z", 3]];
var obj = Object.fromEntries( entries );
console.log( obj ); // {x: 1, y: 2, z: 3}
复制代码
之前我们使用 entries 就能很容易地过滤和映射对象值,但将条目放回到对象表单却很麻烦。这里就可以使用 fromEntries 简化工作了。
var obj = { x: 1, y: 2, z: 3 };
// [["x", 1],["y", 2],["z", 3]]
var objEntries = Object.entries( obj );
// [["x", 1],["z", 3]]
var filtered = objEntries.filter(
( [key, value] ) => value % 2 !== 0 // select odd
);
console.log( Object.fromEntries( filtered ) ); // {x: 1, z: 3}
复制代码
当我们使用 Map 按插入顺序存储键值对时,内部数据结构与条目格式类似。我们可以使用 fromEntries 轻松地从 Map 构造一个对象。
var m = new Map([[“x”,1],[“y”,2],[“z”,3]]);
console.log(m); // {“x”=> 1,“y”=> 2,“z”=> 3}
console.log(Object.fromEntries(m)); // {x:1,y:2,z:3}
复制代码
支持范围——TC39:阶段 4;Chrome:73+;Firefox:63+;Node:12+
globalThis
之前我们很熟悉 JavaScript 中的 this 关键字。它没有确定的值,其值取决于访问它的上下文。在任何环境中,当从程序的最顶层上下文访问时 this 指向全局对象,这就是所谓的全局 this。
例如,在 JavaScript 中全局 this 是 window 对象,你可以在 JavaScript 文件的顶部(最外层上下文)或 JavaScript 控制台内添加 console.log(this)语句来验证这一点。
this 全局值在 Node.js 内部会指向 global 对象,而在 web worker 内部会指向 web worker 本身。但是要获取全局 this 值不太容易,因为我们不能在所有位置使用 this 关键字;例如,在类构造函数中 this 值指向类实例。
因此其他环境为我们提供了像 self 这样的关键字,与 JavaScript 和 web worker 中的全局 this 一样;而在 Node.js 中使用的是 global。使用这些替代关键字时,我们可以创建一个通用函数来返回全局 this 值。
const getGlobalThis = () => {
if (typeof self !== 'undefined') return self;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof this !== 'undefined') return this;
throw new Error('Unable to locate global `this`');
};
var globalThis = getGlobalThis();
复制代码
但是用这个 polyfill 获取全局 this 对象会出问题,这篇文章解释了原因。为了解决这个问题,JavaScript 现在提供了 globalThis 关键字,它可以从任何地方返回全局 this 对象。
var obj = { fn: function() {
console.log( 'this', this === obj ); // true
console.log( 'globalThis', globalThis === window ); // true
} };
obj.fn();
复制代码
支持范围——TC39:阶段 3;Chrome:71+;Firefox:65+;Node:12+
稳定排序
我们对数组排序时,ECMAScript 不会为 JavaScript 引擎提出排序算法,而只会强制执行排序 API 的语法。因此排序性能和/或排序稳定性会随着浏览器或 JavaScript 引擎的不同而变化。
但现在 ECMAScript 强制数组排序算法保持稳定。这个答案介绍了排序稳定性更新。简而言之,如果排序结果(变异数组)中那些不受排序影响的项目顺序不变,与一开始插入的顺序一致,则排序算法就是稳定的。我们来看一个例子吧。
var list = [
{ name: 'Anna', age: 21 },
{ name: 'Barbra', age: 25 },
{ name: 'Zoe', age: 18 },
{ name: 'Natasha', age: 25 }
];
// possible result
[
{ name: 'Natasha', age: 25 }
{ name: 'Barbra', age: 25 },
{ name: 'Anna', age: 21 },
{ name: 'Zoe', age: 18 },
]
复制代码
如上所示,在 list 数组中,名为 Barbra 的对象位于名为 Natasha 的对象之前。由于这些对象有着相同的年龄,我们希望排序结果中它们保持相同的顺序,但有时结果并非如此。排序算法的结果会取决于你使用的 JavaScript 引擎。
但是现在,所有现代浏览器和 Node.js 默认使用 sort 方法进行稳定排序。这将始终产生以下结果。
// stable sort result
[
{ name: 'Barbra', age: 25 },
{ name: 'Natasha', age: 25 }
{ name: 'Anna', age: 21 },
{ name: 'Zoe', age: 18 },
]
复制代码
一些 JavaScript 引擎以前支持稳定排序,但仅适用于较小的数组。为了提高大型数组的性能,他们可能会使用更快的算法并牺牲排序稳定性。
支持范围——Chrome:70+;Firefox:62+;Node:12+
国际化 API
国际化 API 是由 JavaScript 中的 ECMAScript 标准提供的 API,用于格式化指定语言中的数字、字符串、日期和时间。此 API 在 Intl 对象上可用。此对象提供构造函数,以便为指定的区域设置创建与区域相关数据的格式化程序。可在此处查看支持的区域设置列表。
Intl.RelativeTimeFormat
在许多应用程序中,我们通常需要以相对格式显示时间,例如 5 分钟前、昨天、1 周前等。当我们的网站需要区分不同区域的显示内容时,我们需要在分发包中存放所有可能的相对时间输出组合 。
JavaScript 现在在 Intl 对象上提供了 RelativeTimeFormat9(locale, config)构造函数,它允许你为特定的区域设置创建时间格式化程序。这将创建一个具有 format(value, unit)原型方法的对象来生成时间格式。
// español (spanish)
var rtfEspanol= new Intl.RelativeTimeFormat('es', {
numeric: 'auto'
});
log( rtfEspanol.format( 5, 'day' ) ); // dentro de 5 días
log( rtfEspanol.format( -5, 'day' ) ); // hace 5 días
log( rtfEspanol.format( 15, 'minute' ) ); // dentro de 15 minutos
复制代码
支持范围——TC39:阶段 3;Chrome:71+;Firefox:65+;Node:12+
Intl.ListFormat
ListFormat API 允许我们将列表中的项目基于 and 或 or 格式组合在一起。例如,[apples,mangoes,bananas]使用并列格式就是 apples,mangoes and bananas,使用分离格式就是 apples,mangoes or bananas。
首先,我们需要根据区域环境从 ListFormat(locale, config)构造函数创建格式化程序实例,并使用 format(list)原型方法生成特定于区域环境的列表格式。
// español (spanish)
var lfEspanol = new Intl.ListFormat('es', {
type: 'disjunction'
});
var list = [ 'manzanas', 'mangos', 'plátanos' ];
log( lfEspanol.format( list ) ); // manzanas, mangos o plátanos
复制代码
支持范围——TC39:阶段 3;Chrome:72+;Node:12+
Intl.Locale
除了语种名称外,区域设置通常还有很多内容,如日历类型、小时制、语言等。Intl.Locale(localeId, config)构造函数用来基于提供的配置生成格式化的语言环境字符串。它创建的对象包含所有区域设置属性,并暴露 toString 原型方法以获取格式化的区域设置字符串。
const krLocale = new Intl.Locale( 'ko', {
script: 'Kore', region: 'KR',
hourCycle: 'h12', calendar: 'gregory'
} );
log( krLocale.baseName ); // ko-Kore-KR
log( krLocale.toString() ); // ko-Kore-KR-u-ca-gregory-hc-h12
复制代码
在此处(https://unicode.org/reports/tr35/#unicode_locale_id)了解区域设置标识符和 Unicode 区域设置标记。
支持范围——TC39:阶段 3;Chrome:74+;Node:12+
Promise
之前,我们在 Promise 构造函数上有 all 和 race 两种静态方法。Promise.all([… promises])返回一个 promise,它在输入的所有 promises 解析后才解析,输入的任何 promise 被拒绝时它也会被拒绝。Promise.race([… promises])返回一个 promise,输入的任何 promise 解析后它就会解析,输入的任何 promise 被拒绝后它也会被拒绝。
我们迫切需要一个静态方法来返回一个 promise,它要在所有 promise 完成后(解析或拒绝)解析。我们还需要一个类似 race 的方法来返回一个 promise,等输入的任何 promise 解析后它就会解析。
Promise.allSettled
Promise.allSettled 方法获取一组 promise,并在所有 promise 都被解析或拒绝后解析。因此,此方法返回的 promise 不需要 catch 回调,因为它总是会解析。then 回调按照各个 promise 的顺序接收每个 promise 的 status 和 value。
var p1 = () => new Promise(
(resolve, reject) => setTimeout( () => resolve( 'val1' ), 2000 )
);
var p2 = () => new Promise(
(resolve, reject) => setTimeout( () => resolve( 'val2' ), 2000 )
);
var p3 = () => new Promise(
(resolve, reject) => setTimeout( () => reject( 'err3' ), 2000 )
);
var p = Promise.allSettled( [p1(), p2(), p3()] ).then(
( values ) => console.log( values )
);
// Output
[ {status: "fulfilled", value: "val1"}
{status: "fulfilled", value: "val2"}
{status: "rejected", value: "err3"}
]
复制代码
支持范围——TC39:阶段 3;Chrome:76+
Promise.any
Promise.any 方法类似于 Promise.race,但是只要任何 promise 被拒绝,后者返回的 promise 就不会执行 catch 块。相比之下,前者等任何 promise 解析后它返回的 promise 也会解析。如果没有解析任何 promise,catch 块将被执行。如果有任何 promise 先解析了,就会执行 then 块。
支持范围——TC39:阶段 1
观看本文视频
英文原文:https://itnext.io/whats-new-in-javascript-google-i-o-2019-summary-d16bd230841
评论