2019 谷歌 I/O 大会上提出的 JavaScript 新特性,你错过了吗?

本文总结了 2019 年谷歌 I/O 大会上 Mathias 和 Sathya 提出来的 JavaScript 新规范功能。

本文从对线程认识的简要刷新、事件循环、关于事件循环的常见问题和错误认识三个不同层面进行说明,进一步挖掘了 Node 的核心。

正则表达式 lookbehind

正则表达式( RegEx正则表达式 )在任何语言里都是强大的功能。在字符串中搜索复杂的模式时正则表达式就能大显身手了。之前 JavaScript 中的正则表达式实现已经非常齐全了,唯一缺少的就是 lookbehind

Lookahead

首先我们来了解一下正则表达式中的 lookahead 是什么含义,JavaScript 已经支持 lookahead 了。正则表达式中的 lookahead 语法允许你在字符串中选出的模式具有以下属性:另一个已知模式正好紧靠这个模式或不与其相邻,或者在这个模式 之后 。例如在字符串“MangoJuice,VanillaShake,GrapeJuice”中,我们可以使用正向 lookahead 语法来查找旁边有 J​​uice 的单词,即 MangoGrape

有两种类型的 lookahead,分别是正向 lookahead 和负向或否定的 lookahead。

正向 lookahead

正向 lookahead 选出的模式具有以下属性:另一个已知模式位于选出模式之后。正向 lookahead 的语法如下。

复制代码

/[a-zA-Z]+(?=Juice)/

上面的模式选出了大写或小写字母的单词,单词旁边都会有 Juice 。不要把它和正则表达式中的捕获组混淆。Lookahead 和 Lookbehind 都是写在括号里的,但它们没有被捕获。下面看一个正向 lookahead 的实际例子。

复制代码

consttestString ="MangoJuice, VanillaShake, GrapeJuice";
consttestRegExp =/[a-zA-Z]+(?=Juice)/g;
constmatches = testString.match( testRegExp
);
console.log( matches );// ["Mango", "Grape"]

负向 lookahead

类似地,上面的例子中如果使用 负向 lookahead ,就是选出所有后面没有 Juice 的单词。负向 lookahead 的语法与正向 lookahead 的语法类似,但有一处不同。我们需要把 = 符号换成! 符号。

复制代码

/[a-zA-Z]+(?!Juice)/

上面的正向表达式模式会选出所有后面不跟着 Juice 的单词。但上面的模式会选出给定字符串中的所有单词,因为给定字符串中的所有单词都 不以 Juice 结尾 ,因此我们需要更具体些的规则。

复制代码

/(Mango|Vanilla|Grape)(?!Juice)/

这种模式将选出 MangoVanillaGrape 几个单词,它们后面没有跟着 Juice。来看具体的代码。

复制代码

consttestString ="MangoJuice, VanillaShake, GrapeJuice";
consttestRegExp=/(Mango|Vanilla|Grape)(?!Juice)/g;
constmatches = testString.match( testRegExp
);
console.log( matches );// ["Vanilla"]

Lookbehind

与 lookahead 类似,Lookbehind 也是正则表达式中的一种语法,用它选出的模式具有以下属性:字符串中的某个已知模式位于或不在它的 前面 。例如,在字符串“FrozenBananas,DriedApples,FrozenFish”中,我们可以使用正向 lookbehind 来找到前面有 Frozen 的单词,比比如 BananasFish

与 lookahead 类似,这里也有一个正向 lookbehind 和负向或否定 lookbehind。

正向 lookbehind

正向 lookbehind 选出的模式具有以下属性:另一个已知模式位于它的前面。正向 lookbehind 的语法如下。

复制代码

/(?<=Frozen)[a-zA-Z]+/

lookbehind 的模式类似于 lookahead,但带有额外的 < 符号,表示在前。上面的模式会选出所有以 Frozen 开头的单词或者在前面有 Frozen 的单词。来看具体的操作。

复制代码

consttestString ="FrozenBananas, DriedApples, FrozenFish";
consttestRegExp =/(?<=Frozen)[a-zA-Z]+/g;
constmatches = testString.match( testRegExp
);
console.log( matches );// ["Bananas", "Fish"]

负向 lookbehind

负向 lookbehind 选出的模式具有以下属性:另一个已知模式 不在 它的前面。例如要选出“FrozenBananas,DriedApples,FrozenFish”字符串中前面没有 Frozen 的单词,我们将使用以下语法。

复制代码

/(?<!Frozen)[a-zA-Z]+/

但上面的模式将选出字符串中的所有单词,因为所有单词前面都没有 Frozen(FrozenBannanas 这样的单词这里会被视为一整个单词),我们需要更具体一些。

复制代码

/(?<!Frozen)(Bananas|Apples|Fish)/

写在代码中:

复制代码

consttestString ="FrozenBananas, DriedApples, FrozenFish";
consttestRegExp =/(?<!Frozen)(Bananas|Apples|Fish)/g;
constmatches = testString.match( testRegExp
);
console.log( matches );// ["Apples"]

支持范围——TC39:阶段 4;Chrome:62+;Node:8.10.0+

类字段

类字段(class field)是一种新的语法,用来从类构造函数外部定义实例(对象)的属性。有两种类型的类字段,公共类字段和私有类字段。

公共类字段

之前我们必须在类构造函数中定义对象上的属性。这些属性是公共的,意味着可以在类(对象)的实例上访问它们。

复制代码

classDog{
constructor() {
this.name ='Tommy';
}
}

每当我们有一个扩展父类的类时,必须先从构造函数中调用 super,然后才能在子类上添加属性,如下所述。

复制代码

classAnimal{}
classDogextendsAnimal{
constructor() {
super();// call super before using `this` in constructor
this.sound ='Woof!Woof!';
}
makeSound() {
console.log(this.sound );
}
}
// create instance
const tommy =newDog();
tommy.makeSound();// Woof! Woof!

现在有了公共类字段语法,我们就可以在类的构造函数之外定义类字段,JavaScript 将隐式调用 super。

复制代码

classAnimal{}
classDogextendsAnimal{
sound ='Woof!Woof!';// public class field
makeSound() {
console.log(this.sound );
}
}
// create instance
const tommy =newDog();
tommy.makeSound();// Woof! Woof!

当 JavaScript 隐式调用 super 时,它会在实例化类时传递用户提供的所有参数(这是标准的 JavaScript 行为,与私有类字段无关)。因此,如果你的父构造函数需要定制参数,请确保手动调用 super。

复制代码

classAnimal{
constructor( ...args ) {
console.log('Animalargs:', args );
}
}
classDogextendsAnimal{
sound ='Woof!Woof!';// public class field
makeSound() {
console.log(this.sound );
}
}
// create instance
const tommy =newDog('Tommy','Loves','Toys!' );
tommy.makeSound();// Animal args: [ 'Tommy', 'Loves', 'Toys!' ]

支持范围——TC39:阶段 3;Chrome:72+;Node:12+

私有类字段

众所周知,JavaScript 没有 publicprivateprotected 之类的属性修饰符。默认情况下,对象上的所有属性都是公共的,这意味着任何人都可以访问它们。想要定义一个对外界隐藏的属性,最接近的方法是使用 Symbol 这个属性名称。你可能会使用 _ 前缀来表示应该是私有的属性,但它只是一种表示法,不能解决问题。

现在有了私有类字段,我们可以让类属性只能在类中访问,并防止它们反映在实例(对象)上。来看前面的一个例子,先看一个暴露的属性。

复制代码

class Dog {
_sound = 'Woof! Woof!'; // this isprivate

makeSound() {
console.log( this._sound );
}
}
// createinstance
const tommy =newDog();
console.log( tommy._sound ); // Woof! Woof!

添加 _ 前缀并不能解决我们的问题。私有类字段的定义方式与定义公共类字段的方式相同,但我们不必添加下划线前缀,而是添加#前缀。访问对象上的私有属性将导致 SyntaxError: Undefined private field。

复制代码

class Dog {
#sound = 'Woof! Woof!'; // this is private
makeSound() {
console.log( this.#sound );
}
}
// createinstance
const tommy =newDog();
tommy.makeSound() // Woof! Woof!
//console.log( tommy.#sound ); // SyntaxError

私有属性只能在定义它们的类中访问。因此在父类或子类内无法访问私有属性。

我们还可以使用未定义的值定义私有(和公共)属性。

复制代码

classDog{
#name;
constructor( name ) {
this.#name = name;
}
showName() {
console.log(this.#name );
}
}
// create instance
consttommy =newDog('Tommy');
tommy.showName();// Tommy

支持范围——TC39:阶段 3;Chrome:74+;Node:12+

string.matchAll

我们在 string 数据类型上有 match 原型方法,它根据给定的正则表达式或关键字返回字符串中的匹配模式。

复制代码

constcolors ="#EEE, #CCC, #FAFAFA, #F00, #000";
constmatchColorRegExp =/([A-Z0-9]+)/g;
console.log( colors.match( matchColorRegExp ) );
// Output:
["EEE","CCC","FAFAFA","F00","000"]

但这种方法不提供其他附加信息,例如字符串中每个匹配的索引。删除 g 标志后可以生成其他信息,但之后我们只能获得第一个匹配项。

复制代码

const colors ="#EEE, #CCC, #FAFAFA, #F00, #000";
constmatchColorRegExp = /#([A-Z0-9]+)/;
console.log( colors.match(matchColorRegExp ) );
// Output: (result shortnedforviewing purpose)
["#EEE","EEE", index:0, input:""]

最后,我们需要在正则表达式对象和语法上使用.exec 方法,这样写起来没那么复杂。我们需要使用 while 循环,直到 exec 返回 null 为止。但要注意,exec 不会返回迭代器。

复制代码

const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
//instrictmode,
// Uncaught ReferenceError: matchisnotdefined
while( match = matchColorRegExp.exec( colors ) ) {
console.log( match );
}
// Output: (result shortnedforviewing purpose)
["#EEE", "EEE",index:0,input: ""]
["#CCC", "CCC",index:6,input: ""]
["#FAFAFA", "FAFAFA",index:12,input: ""]
["#F00", "F00",index:21,input:input: ""]
["#000", "000",index:27,input:input: ""]

为了解决这个问题,我们现在有了 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:""]
["#CCC","CCC",index:6,input:""]
["#FAFAFA","FAFAFA",index:12,input:""]
["#F00","F00",index:21,input:input:""]
["#000","000",index:27,input:input:""]

支持范围——TC39:阶段 4;Chrome:73+;Firefox:67+;Node:12+

命名捕获组

与其他语言相比,JavaScript 中捕获或捕获组的概念略有不同。每当我们在括号内放置一个正则表达式模式(lookahead 和 lookbehind 除外)时,它就会变成一个捕获组,所有匹配的模式都会反映在匹配的输出项中。

在前面的示例中,下面数组的第一项是正则表达式模式的完整匹配,而第二项是捕获组的匹配值。

复制代码

["#EEE","EEE", index:0, input:""]

如果有多个捕获组,它们将连续显示在结果中。我们来看一个例子。

复制代码

conststr ="My name is John Doe.";
constmatchRegExp = /Mynameis([a-z]+) ([a-z]+)/i;
constresult= str.match( matchRegExp );console.log(result);
// error,ifresultisnull
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 (?[a-z]+) (?[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 中,我们可以使用 _ 分隔符来分隔数字的各个部分,以增强可读性。

复制代码

varbillion=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 内置原始数据类型来表示大整数。

复制代码

varlarge=BigInt(9007199254740991);
console.log(large); //9007199254740991n
console.log( typeoflarge); //bigint

JavaScript 会在整数的末尾添加 n 下标以表示 BigInt 整数形式。因此我们只需在整数的最末尾附加 n 就能写成 BigInt 了。

现在我们有了 BigInt,就可以安全地对具有 bigint 数据类型的大数字执行数学运算了。

复制代码

varlarge=9007199254740991n;
console.log(large+10n); //9007199254741001n

使用 number 数据类型的数字与使用 bigint 数据类型的数字不同,因为 bigint 只能表示整数。因此程序不允许 bigint 和 number 数据类型之间的算术运算。

BigInt 函数可以接受任何类型的数字,如整数、二进制、十六进制、八进制等。它会在内部统一转换为十进制。

BigInt 还支持数字分隔符。

复制代码

varlarge=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 变换数组,然后将其展平。例如计算一些整数的平方。

复制代码

varnums = [1,2,3];
varsquares = 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 的深度。

复制代码

varnums = [1,2,3];
varmakeSquare =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 静态方法,它会将条目转换回对象。

复制代码

varentries = [["x",1],["y",2],["z",3]];
varobj =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 构造一个对象。

复制代码

varm =newMap([[“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(typeofself !=='undefined')returnself;
if(typeofwindow!=='undefined')returnwindow;
if(typeofglobal!=='undefined')returnglobal;
if(typeofthis!=='undefined')returnthis;
thrownewError('Unable to locate global `this`');
};
var globalThis = getGlobalThis();

但是用这个 polyfill 获取全局 this 对象会出问题, 这篇文章 解释了原因。为了解决这个问题,JavaScript 现在提供了 globalThis 关键字,它可以从任何地方返回全局 this 对象。

复制代码

varobj = {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)
varlfEspanol =newIntl.ListFormat('es', {
type:'disjunction'
});
varlist= ['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 =newIntl.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 =()=>newPromise(
(resolve, reject)=>setTimeout(()=>resolve('val1'),2000)
);

var p2 =()=>newPromise(
(resolve, reject)=>setTimeout(()=>resolve('val2'),2000)
);

var p3 =()=>newPromise(
(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