2019 谷歌 I/O 大会上提出的 JavaScript 新特性,你错过了吗?
本文总结了 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 的语法如下。
复制代码
/[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)/
这种模式将选出 Mango 、 Vanilla 或 Grape 几个单词,它们后面没有跟着 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 的单词,比比如 Bananas 和 Fish 。
与 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 没有 public 、 private 和 protected 之类的属性修饰符。默认情况下,对象上的所有属性都是公共的,这意味着任何人都可以访问它们。想要定义一个对外界隐藏的属性,最接近的方法是使用 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