ECMAScript6 新的数据结构、Class语法、Proxy和Promise,看完我写的ES6,马上会的,你就是高手
文章目录
ES6
我们已经了解了以下复杂的数据结构:
- 存储带键的数据(keyed)集合的对象。
- 存储有序集合的数组。
但这还不足以应对现实情况。这就是为什么存在 Map
和 Set
。
新的数据结构
Map
Map 是一个带键的数据项的集合,就像一个 Object
一样。 但是它们最大的差别是 Map
允许任何类型作为键(key)。
它的方法和属性如下:
-
new Map()
—— 创建 map。 -
map.set(key, value)
—— 根据键存储值。 -
map.get(key)
—— 根据键来返回值,如果map
中不存在对应的key
,则返回undefined
。 -
map.has(key)
—— 如果key
存在则返回true
,否则返回false
。 -
map.delete(key)
—— 删除指定键的值。 -
map.clear()
—— 清空 map。 -
map.size
—— 返回当前元素个数。
例如:
// 创建一个map数据集合
let map = new Map();
var obj = {};
// 往里面添加内容
map.set("1", "string"); // 字符串键
map.set(1, "number"); // 数字键
map.set(true, "boolean"); // 布尔值键
map.set(obj, "object"); // 对象键
console.log(map.get(1)); // number
console.log(map.get("1")); // string
// map 是根据类似于 === 的算法来比较是否是键名的,但是NaN也可以被识别
console.log(map.get(obj)); // object
console.log(map.size); // 3
每一次 map.set
调用都会返回 map 本身,所以我们可以进行“链式”调用:
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
Map 迭代
如果要在 map
里使用循环,可以使用以下三个方法:
-
map.keys()
—— 遍历并返回所有的键(returns an iterable for keys), -
map.values()
—— 遍历并返回所有的值(returns an iterable for values), -
map.entries()
—— 遍历并返回所有的实体(returns an iterable for entries)[key, value]
,for..of
在默认情况下使用的就是这个。
例如:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 遍历所有的键(vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 遍历所有的值(amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// 遍历所有的实体 [key, value]
for (let entry of recipeMap) { // 与 recipeMap.entries() 相同
alert(entry); // cucumber,500 (and so on)
}
除此之外,Map
有内置的 forEach
方法,与 Array
类似:
// 对每个键值对 (key, value) 运行 forEach 函数
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
与普通对象 Object
的不同点:
- 任何类型值都可以作为键。
- 有其他的便捷方法,如
size
属性。
Set
Set
是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。
它的主要方法如下:
-
new Set(iterable)
—— 创建一个set
,如果提供了一个iterable
对象(通常是数组),将会从数组里面复制值到set
中。 -
set.add(value)
—— 添加一个值,返回 set 本身 -
set.delete(value)
—— 删除值,如果value
在这个方法调用的时候存在则返回true
,否则返回false
。 -
set.has(value)
—— 如果value
在 set 中,返回true
,否则返回false
。 -
set.clear()
—— 清空 set。 -
set.size
—— 返回元素个数。
它的主要特点是,重复使用同一个值调用 set.add(value)
并不会发生什么改变。这就是 Set
里面的每一个值只出现一次的原因。
var s = new Set();
// [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
console.log(s)
// [2 3 5 4]
Set迭代
Map
中用于迭代的方法在 Set
中也同样支持:
-
set.keys()
—— 遍历并返回所有的值(returns an iterable object for values), -
set.values()
—— 与set.keys()
作用相同,这是为了兼容Map
, -
set.entries()
—— 遍历并返回所有的实体(returns an iterable object for entries)[value, value]
,它的存在也是为了兼容Map
。
我们可以使用 for..of
或 forEach
来遍历 Set:
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set){
console.log(value);
}
// 与 forEach 相同:
set.forEach((value, valueAgain, set) => {
console.log(value);
});
**Set实现数组去重 **
var arr = [1,2,3,4,5,3,3,2,2,1];
var newArr = [...new Set(arr)];
console.log(newArr); // [1, 2, 3, 4, 5]
class
在现代 JavaScript 中,有一个更为高级的类构造方式,它引入了对于面向对象编程很有用的功能。
Class语法
class MyClass {
prop = value; // 属性
constructor() { ... } // 构造器
method1() { ... } // 方法
method2() { ... }
method3() { ... }
...
}
然后通过 new MyClass()
来创建具有上述列出的所有方法的新对象。
通过 new
关键词创建的对象会自动调用 constructor()
方法,因此我们可以在 constructor()
里初始化对象。例如:
class Person {
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
var person = new Person("李白");
person.sayName();
注意:类方法之间没有逗号。
解析:在 js 中,类是一个函数,如上例 typeof Person
值是 function
。class Person {...}
构造器内部为我们做了什么:
- 创建一个以
Person
为名称的函数,这是类声明的结果。该函数的代码来自于constructor
方法(如果没有写,那么它就假定为空) - 储存类中的方法,例如
Person.prototype
中的sayName
。
与之前的构造函数不同的是:
- 类不能像构造函数那样直接调用,必须通过
new
来调用 - 类方法不可枚举
- 类的属性不会共享,只有方法共享
- 类总是使用
use strict
,在类构造中的所有代码都将自动进入严格模式
静态属性
我们可以把一个属性或方法赋值给一个类方法,而不是赋给它的 "原型对象"
。这样的属性和方法我们称为静态的。
在类里面,他们在前面添加 “static” 关键字,例如:
class User {
static n = 10;
static staticMethod() {
console.log(this === User);
}
}
User.n = 10;
User.staticMethod(); // true
var user = new User();
console.log(user.n); // undefined
console.log(user.staticMethod); // undefined
静态的属性和方法可被继承
计算属性
类似与对象字面量,类包括 getters/setters,计算属性(computed properties)等。
class User {
constructor(name){
this.name = name;
}
get name(){
return this._name;
}
set name(v){
if(v.length < 4){
alert("Name is too short.");
}
this._name = v;
}
[this.name + "hello"](){
console.log("hello");
}
}
let user = new User("李白"); // Name is too short
let userMore = new User("libai");
类表达式
如函数一样,类可以在另一个表达式中定义,传递(passed around),返回(returned),调用(assigned)等。例如:
var User = class {
sayHi() {
alert("Hello");
}
};
类似于命名函数表达式(Named Function Expressions),类表达式可能也可能没有名称。
如果类表达式有名称,它仅在类内部可见:
// “命名类表达式”
// (规范中没有这样的术语,但是它和命名函数表达式类似)
var User = class MyClass {
sayHi() {
alert(MyClass); // MyClass 仅在其内部可见
}
};
new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容
alert(MyClass); // 错误,MyClass 在外部不可见
function makeClass(phrase) {
// 声明并返回类
return class {
sayHi() {
alert(phrase);
};
};
}
// 创建新类
let User = makeClass("Hello");
new User().sayHi(); // Hello
类继承
如果一个类要继承自另一个类,我们需要在 {..}
前指定 extends
和父类。例如:
class Animal{
// 构造函数
constructor(name){
this.name = name;
}
run(){
console.log("running ...");
}
}
// 通过指定“extends Animal”让 Dog 继承自 Animal
class Dog extends Animal{
skill(){
console.log("lookHouse ...");
}
}
var dog = new Dog("旺财");
dog.run();
console.log(dog.name);
extends
允许后接任何表达式
类语法不仅可以指定一个类,还可以指定 extends
之后的任何表达式。
function f(phrase) {
return class {
sayHi() { alert(phrase) }
}
}
class User extends f("Hello") {}
new User().sayHi(); // Hello
通常来说,我们不希望完全替换父类的方法,而是希望基于它做一些调整或者功能性的扩展。我们在我们的方法中做一些事情,但是在它之前/之后或在执行过程中调用父类方法。
为此,类提供了 "super"
关键字。
- 执行
super.method(...)
调用父类方法 - 执行
super(...)
调用父类构造函数(只能在子类的构造函数中运行)
继承类的 constructor 必须调用 super(...)
,并且一定要在使用 this
之前调用。
例如,让 Dog
拥有自己的颜色,然后添加一个自己的跑动的方法:
class Animal{
// 构造函数
constructor(name){
this.name = name;
}
run(){
console.log("running ...");
}
}
// 通过指定“extends Animal”让 Dog 继承自 Animal
class Dog extends Animal{
constructor(name, color){
// 如果想要书写自己的构造函数, super一定要有
super(name); // 调用父类Animal的构造函数
this.color = color;
}
skill(){
console.log("lookHouse ...");
}
run(){
super.run(); // 调用父类Animal的跑方法
console.log("添加的独特的跑步方法");
}
}
var dog = new Dog("旺财", "yellow");
dog.run();
console.log(dog);
类作业
创建一个继承自 Clock
的新的类 ExtendedClock
,并添加参数 precision
— 每次输出结果的间隔的毫秒数,默认是 1000
(1 秒)。
- 你的代码应该在
extended-clock.js
文件里。 - 不要修改原有的
clock.js
。请扩展它。
- 面向对象案例
<script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/1.10.1/jquery.min.js"></script>
<style>
.clearfix:after {
content: " ";
display: block;
height: 0;
clear: both;
}
.clearfix {
zoom: 1;
}
*{
list-style: none;
}
.tab{
border-bottom: 1px solid red;
}
.tab li{
float: left;
padding: 10px;
}
.tab li:hover{
color: red;
}
.tab .on{
color: red;
}
.box li{
display: none;
}
.box .on{
display: block;
}
</style>
</head>
<body>
<div id="main">
</div>
<div id="main1">
</div>
<script>
class Tab {
constructor(id , arrs){
this.id = id;
this.arrs = arrs;
this.init();
}
init(){
this.initBox(); // 初始化 容器
this.initTop(); // 初始化 tab头部栏
this.initBot(); // 初始化 tab底部栏
}
initBox(){
var str = `<div class="tab clearfix"></div><div class="box clearfix"></div>`
$(`#${this.id}`).html(str) ;
}
initTop(){ // 初始化tab栏
var ul = document.createElement("ul")
var str="";
for(var i = 0 ; i< this.arrs.length ; i ++){
str +=`<li class="${i == 0 ? "on" : ""}" οnclick="${this.toggle(this.id , this)}">${this.arrs[i].tabname}</li>`
}
ul.innerHTML = str
$(`#${this.id} .tab`).html(ul);
}
initBot(){
var ul = document.createElement("ul")
var str="";
for(var i = 0 ; i< this.arrs.length ; i ++){
str +=`<li class="${i == 0 ? "on" : ""}">${this.arrs[i].info}</li>`
}
ul.innerHTML = str
$(`#${this.id} .box`).html(ul);
}
toggle(id , that){
$(`#${id} .tab`).on("click","ul li",function(){
$(this).addClass("on").siblings().removeClass("on");
$(`#${that.id} .box ul li`).removeClass("on").eq($(this).index()).addClass("on");
})
}
}
var arrs = [
{
tabname:"张三",
info:"张三位于郑州"
},
{
tabname:"李四",
info:"李四位于上海"
},
{
tabname:"王五",
info:"王五位于北京"
},
]
var tabs = new Tab("main" , arrs)
var tabs1 = new Tab("main1" , arrs)
</script>
</body>
</html>
</script>
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,由此我们可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
语法:
let proxy = new Proxy(target, handler)
-
target
—— 是要包装的对象,可以是任何东西,包括函数。 -
handler
—— 代理配置:声明了代理target
的哪些操作 -
proxy
是被代理完成之后返回的对象
对 proxy
进行操作,如果在 handler
中存在相应的设置,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。
对于每个内部方法,此表中都有一个设置:可用于添加到 new Proxy
的 handler
参数中以拦截操作的方法名称:
内部方法 | Handler 方法 | 何时触发 |
---|---|---|
[[Get]] |
get |
读取属性 |
[[Set]] |
set |
写入属性 |
[[HasProperty]] |
has |
in 操作符 |
[[Delete]] |
deleteProperty |
delete 操作符 |
[[Call]] |
apply |
函数调用 |
[[Construct]] |
construct |
new 操作符 |
[[GetPrototypeOf]] |
getPrototypeOf |
Object.getPrototypeOf |
[[SetPrototypeOf]] |
setPrototypeOf |
Object.setPrototypeOf |
[[IsExtensible]] |
isExtensible |
Object.isExtensible |
[[PreventExtensions]] |
preventExtensions |
Object.preventExtensions |
[[DefineOwnProperty]] |
defineProperty |
Object.defineProperty, Object.defineProperties |
[[GetOwnProperty]] |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor, for..in , Object.keys/values/entries
|
[[OwnPropertyKeys]] |
ownKeys |
Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in , Object/keys/values/entries
|
注意:要使得 Proxy
起作用,必须针对 Proxy
实例进行操作,而不是针对目标对象进行操作。
如果 handler
没有设置任何拦截,那就等同于直接通向原对象。
最常见的设置是用于读取/写入的属性。
get
要拦截读取操作,handler
应该有 get(target, property, receiver)
方法。
读取属性时触发该方法,参数如下:
-
target
— 是目标对象,该对象被作为第一个参数传递给new Proxy
, -
property
— 目标属性名, -
receiver
— 如果目标属性是一个 getter 访问器属性,则receiver
就是本次读取属性所在的this
对象。通常,这就是proxy
对象本身。可选。
// 如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name " + propKey + " does not exist.");
}
}
});
proxy.name // "张三"
proxy.age // 抛出一个错误
set
当写入属性时 set
设置被触发。
set(target, property, value, receiver)
:
-
target
— 是目标对象,该对象被作为第一个参数传递给new Proxy
, -
property
— 目标属性名称, -
value
— 目标属性的值, -
receiver
— 与get
设置类似,仅与 setter 访问器属性相关。
如果写入操作(setting)成功,set
设置应该返回 true
,否则返回 false
(触发 TypeError
)。
var person = {
name: "张三",
age:20
};
var proxy = new Proxy(person, {
set(target, key, val){
if(key == "age"){
if(val>150){
throw new Error("年龄不符合要求")
}else{
target[key] = val
}
}
}
});
proxy.age = 200;
has
has
方法会拦截 in
调用。
has(target, property)
-
target
— 是目标对象,被作为第一个参数传递给new Proxy
, -
property
— 属性名称
示例如下:
// 创建一个范围对象
let range = {
start: 1,
end: 10
};
// 设置代理,监听in操作符 -> 更换它的逻辑
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end;
}
});
alert(5 in range); // true
alert(50 in range); // false
apply
我们也可以将代理(proxy)包装在函数周围。
apply(target, thisArg, args)
方法能使代理以函数的方式被调用:
-
target
是目标对象(在 JavaScript 中,函数就是一个对象), -
thisArg
是this
的值。 -
args
是参数列表。
例如:
// 之前的延时包装器函数
function delay(f, ms){
return function(...args){
setTimeout(function(){
f.apply(null, args);
}, ms)
}
}
function say(word){
console.log("Hi " + word);
}
let f1000 = delay(say, 1000);
// 包装函数不会转发属性读取/写入操作或者任何其他操作。进行包装后,就失去了对原始函数属性的访问,例如 `name`,`length` 和其他属性
console.log(f1000.length); // 0
Proxy
的功能要强大得多,因为它可以将所有东西转发到目标对象。
让我们使用 Proxy
来替换掉包装函数:
function delay(f, ms){
return new Proxy(f, {
apply(target, thisArg, args){
setTimeout(() => target(...args), ms);
}
})
}
function say(word){
console.log("Hi " + word);
}
let f2000 = delay(say, 2000);
console.log(f2000.length); // 1
受保护的属性
有一个普遍的约定,即以下划线 _
开头的属性和方法是内部的。不应从对象外部访问它们,但实际上我们也是能访问到这样的属性的:
let user = {
name: "李白",
_age: 23
}
console.log(user._age); // 23
我们可以使用代理来防止对以 _
开头的属性的任何访问。
我们将需要以下内容:
-
get
读取此类属性时抛出错误 -
set
写入属性时抛出错误 -
deleteProperty
删除属性时抛出错误 -
ownKeys
在使用for..in
和像Object.keys
这样的的方法时排除以_
开头的属性
let user = {
name: "李白",
_age: 23
}
user = new Proxy(user, {
get(target, prop){
if(prop[0] == '_'){
throw new Error("不能访问私有属性");
}
return target[prop];
},
set(target, prop, val){
if(prop[0] == '_'){
throw new Error("不能访问私有属性");
}
target[prop] = val;
},
deleteProperty(target, prop){
if(prop[0] == '_'){
throw new Error("不能访问私有属性");
}
delete target[prop];
},
ownKeys(target){
return Object.keys(target).filter(item => !(item[0] == "_"));
}
})
// "get" 不允许读取 _age
try{
console.log(user._age);
}catch(e){
console.log("get Error", e);
}
// "set" 不允许写入 _age
try{
user._age = 20;
}catch(e){
console.log("set Error", e);
}
// "deleteProperty" 不允许删除 _age
try{
delete user._age;
}catch(e){
console.log("delete Error", e);
}
// "ownKeys" 将 _age 过滤出去
for(let key in user){
console.log("遍历", key);
}
练习
1、在某些编程语言中,我们可以使用从尾端算起的负值索引访问数组元素。即 array[-N]
与 array[array.length - N]
相同。
像这样:
let array = [1, 2, 3];
array[-1]; // 3,最后一个元素
array[-2]; // 2,从尾端开始向前移动一步
array[-3]; // 1,从尾端开始向前移动两步
创建一个 proxy 来实现该行为。
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
基础语法及理解
let promise = new Promise(function(resolve, reject){
// do somthing async...
})
传递给 new Promise
的函数被称为 executor(执行者)。当 new Promise
被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。
它的参数 resolve
和 reject
是由 JavaScript 自身提供的回调。我们的代码仅在 executor 的内部。
当 executor 获得了结果,无论是早还是晚都没关系,它应该调用以下回调之一:
-
resolve(value)
— 如果任务成功完成并带有结果value
。 -
reject(error)
— 如果出现了 error,error
即为 error 对象。
所以总结一下就是:executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve
,如果出现 error 则调用 reject
。
由 new Promise
构造器返回的 promise
对象具有以下内部属性:
-
state
— 最初是"pending"
,然后在resolve
被调用时变为"fulfilled"
,或者在reject
被调用时变为"rejected"
。 -
result
— 最初是undefined
,然后在resolve(value)
被调用时变为value
,或者在reject(error)
被调用时变为error
。
let p1 = new Promise(resolve => {
setTimeout(function(){
resolve("success");
console.log(p1);
}, 2000);
})
console.log(p1);
let p2 = new Promise((resolve, reject) => {
setTimeout(function(){
reject("success");
console.log(p2);
}, 2000);
})
console.log(p2);
注意:
-
executor 只能调用一个
resolve
或一个reject
。任何状态的更改都是最终的。所有其他的再对
resolve
和reject
的调用都会被忽略:let p3 = new Promise((resolve, reject) => { resolve("done"); reject(new Error("…")); // 被忽略 setTimeout(() => resolve("…"), 0); // 被忽略 });
-
resolve/reject
只需要一个参数(或不包含任何参数),并且将忽略额外的参数。
then
语法:
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
.then
的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
.then
的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
例如,以下是对成功 resolved 的 promise 做出的反应:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("成功!"), 1000);
});
// resolve 运行 .then 中的第一个函数
promise.then(
result => alert(result), // 1 秒后显示 "成功!"
error => alert(error) // 不运行
);
在 reject 的情况下,运行第二个:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("报错了!")), 1000);
});
// reject 运行 .then 中的第二个函数
promise.then(
result => alert(result), // 不运行
error => alert(error) // 1 秒后显示 "Error: 报错了!"
);
当前,我们也可以只传入成功的函数。
catch
如果我们只对 error 感兴趣,那么我们可以使用 null
作为第一个参数:.then(null, errorHandlingFunction)
。或者我们也可以使用 .catch(errorHandlingFunction)
,其实是一样的:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("报错!")), 1000);
});
// .catch(func) 与 promise.then(null, func) 一样
promise.catch(alert); // 1 秒后显示 "Error: 报错!"
.catch
能够处理 promise 中的各种 error:在 reject()
调用中的,或者在处理程序中抛出的(thrown)error(必须是同步错误)。
let promise = new Promise((resolve, reject) => {
throw new Error("报错!")
});
// .catch(func) 与 promise.then(null, func) 一样
promise.catch(alert); // 1 秒后显示 "Error: 报错!"
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => { throw new Error("报错!"); }, 1000);
});
// .catch(func) 与 promise.then(null, func) 一样
promise1.catch(alert); // 无法捕获
finally
-
finally
处理函数没有参数。在finally
中,我们不知道 promise 是否成功。一般来说此方法用于执行“常规”的定稿程序(finalizing procedures)。 -
finally
处理函数并不处理 promise 的结果,而是将结果传递给下一个处理程序。
使用1:
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => alert("Promise ready"))
.then(result => alert(result)); // <-- .then 对结果进行处理
使用2:
new Promise((resolve, reject) => {
throw new Error("error");
})
.finally(() => alert("Promise ready"))
.catch(err => alert(err)); // <-- .catch 对 error 对象进行处理
Promise 链
实际上 promise.then
方法返回值也是一个 promise 对象,它可以形成一个链式调用。
then 函数内部的处理函数的返回值会成为下一个 promise 对象的 result,所以将使用它调用下一个 .then
。
调用 promise.then
方法返回的 promise 对象的状态由 then 方法的回调函数执行结果决定:
- 如果回调函数中返回的结果时 非promise 类型的 属性,状态成功,返回值为对象的成功值
- 是 promise 对象,根据对象的返回状态决定
- 抛出错误,状态失败,接受错误内容
例如:
let promise = new Promise(function(resolve, reject){
setTimeout(() => resolve(1), 2000);
}).then(res => {
console.log("第一次执行成功回调", res * 2);
return res * 2;
}).then(res => {
console.log("第二次执行成功回调", res * 2);
return res * 2;
}).then(res => {
console.log("第三次执行成功回调", res * 2);
})
又或者:
let permise = new Promise(resolve => {
setTimeout(function () {
resolve("success");
}, 2000);
}).then(function (res){
console.log("第一个 then 方法", res, "函数内抛出异常");
throw new Error("错误 1998");
}).catch(function (err){
console.log("捕获异常:", err, "函数内返回字符串");
return "success";
}).then(function (res){
console.log("在异常后面继续接受成功的内容",res, "返回一个promise");
return new Promise(resolve => {
setTimeout(function (){
resolve("last")
}, 1000)
})
}).then(function (res){
console.log("最后一站 接收到上一个promise成功的返回值", res);
})
Promise静态方法
Promise.all
假设我们希望并行执行多个 promise,并等待所有 promise 都准备就绪,我们可以使用此方法来实现。
语法:
let promise = Promise.all([...promises...]);
Promise.all
接受一个 promise 数组作为参数(从技术上讲,它可以是任何可迭代的,但通常是一个数组)并返回一个新的 promise。
当所有给定的 promise 都被 settled 时,新的 promise 才会 resolve,并且其结果数组将成为新的 promise 的结果。
例如:
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(console.log);
请注意,结果数组中元素的顺序与其在源 promise 中的顺序相同。即使第一个 promise 花费了最长的时间才 resolve,但它仍是结果数组中的第一个。
注意:如果任意一个 promise 被 reject,由 Promise.all
返回的 promise 就会立即 reject,并且带有的就是这个 error。
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("错误!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(console.log); // Error: 错误!
Promise.allSettled
假如我们组合使用 promise 做请求,即使有一部分失败我们仍想获取其它的数据,此方法很有用。Promise.allSettled
等待所有的 promise 都被 settle,无论结果如何。结果数组具有:
-
{status:"fulfilled", value:result}
对于成功的响应, -
{status:"rejected", reason:error}
对于 error。
例如:
Promise.allSettled([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("错误!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(console.log);
结果为:
[
{status: "fulfilled", value: 1},
{status: "rejected", reason: Error: 错误! at <anonymous>:3:60},
{status: "fulfilled", value: 3}
]
Promise.race
与 Promise.all
类似,但只等待第一个 settled 的 promise 并获取其结果(或 error)。
语法:
let promise = Promise.race(iterable);
例如:
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(console.log); // 1
这里第一个 promise 最快,所以它变成了结果。第一个 settled 的 promise “赢得了比赛”之后,所有进一步的 result/error 都会被忽略。
Async/await
Async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。
Async function
让我们以 async
这个关键字开始。它可以被放置在一个函数前面,如下所示:
async function f(){
return 1;
}
在函数前面的 “async” 这个单词表达了一个简单的事情:即这个函数总是返回一个 promise,函数的返回值决定 promise 对象的完成状态 参考。
f().then(console.log); // 1
我们也可以显式地返回一个 promise,结果是一样的:
async function f() {
return new Promise(resolve => resolve(10));
}
f().then(console.log); // 10
Await
语法如下:
// 只在 async 函数内工作
let value = await promise;
关键字 await
让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果。
这里的例子就是一个 2 秒后 resolve 的 promise:
async function f(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 2000);
})
let result = await promise; // 等待,直到 promise resolve (*)
console.log(result); // done!
}
f();
这个函数在执行的时候,“暂停”在了 (*)
那一行,并在 promise settle 时,拿到 result
作为结果继续往下执行。所以上面这段代码在一秒后显示 “done!”。
总结:
- 函数前面的关键字
async
有两个作用:- 让这个函数总是返回一个 promise
- 允许在该函数内使用
await
- Promise 前的关键字
await
使 JavaScript 引擎等待该 promise settle,然后:- 如果有 error,就会抛出异常——就像那里调用了
throw error
一样 - 否则,就返回结果
- 如果有 error,就会抛出异常——就像那里调用了