Promise 让人又恨又爱的存在,恨是因为面试的时候会围绕它出很多题,又绕又头疼,爱是真香,谁都逃不过真香定律。

1、概念

Promise 是异步编程的一个新的解决方案,阮一峰:ECMAScript 6 入门中给出对 promise 的含义是:所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise 是一个构造函数,它有两个特点:

  1. Promise 有三个状态:pending(进行中)、resolved(已成功)和 rejected(已失败)。并且状态不受外部的影响。
  2. 状态一旦改变就无法修改。只能有两个过程:一个是从 pending 到 resolved 还有一个就是从 pending 到 rejected,不可能从 resolved 到 rejected,一旦成功就不可能再失败了。
  3. Promise 一旦创建就会立即执行,不能中途取消。

2、用法

通常我们来说,Promise 主要是解决异步回调地狱。什么是回调地狱?
从网上找了几张图,大家可以感受一下被回调地狱所支配的恐惧:
回调地狱最大的缺点就是代码可读性差,编写费劲。
接下来我们来看一下 Promise 的基本用法:

1
2
3
4
5
6
7
8
new Promise((resolve, reject)=>{
...
if(success) {
resolve(value);
}else {
reject(error);
}
})

之前说过,Promise 是一个构造函数,它接收一个函数参数,这个函数中接收两个参数,一个是 resolve 还有一个是 rejected,这两个参数均为函数,并且这两个参数不用自己部署,JS 引擎会自动部署。
resolve 函数的作用是当异步函数成功时,将成功后的值传递出去,同时也是将状态从 pending 变为 resolved,reject 函数的作用是当异步函数失败后,将失败的错误信息传递出去,同时也是将状态从 pending 变为 rejected。

2.1、then()

当 Promise 实例创建成功后,可以执行其原型上的 then 方法,then 方法同样接收两个函数参数,第一个是接收的 resolve 回调的结果,第二个是 reject 回调的结果,第二个参数是非必填的。

1
2
3
4
5
6
7
8
9
10
new Promise((resolve, reject) => {
resolve(1);
}).then(
(value) => console.log(value) // 1
);
new Promise((resolve, reject) => {
reject("出现错误");
}).then(
(error) => console.log(error) // 出现错误
);

因为 Promise 的对象时立即创建的,所以在 resolve 和 reject 函数之前的操作都会立即执行:

1
2
3
4
5
6
new Promise((resolve, reject) => {
console.log(2);
resolve(1);
}).then(
(value) => console.log(value) // 2 1
);

Promise 执行 then 方法后会返回回来一个新的 Promise 对象,所以可以采用链式调用。

1
2
3
4
5
new Promise((resolve) => {
resolve(1);
})
.then((value) => value + 1)
.then((value) => console.log(value)); // 2

第一个 then 函数的返回值,可以作为参数传给第二个 then 函数。如果第一个 then 函数返回的依旧是一个 Promise 对象呢?即是一个 Promise 封装的异步操作:

1
2
3
4
5
6
7
8
9
10
new Promise((resolve) => {
resolve(1);
})
.then(
(value) =>
new Promise((resolve) => {
resolve(3);
})
)
.then((value) => console.log(value)); // 3

此时第二个 then 函数传入的参数,即为第一个 then 函数返回的 Promise 对象的 resolved 状态下传递的值。也可以说只有第一个 then 返回的 Promise 执行状态成功时,第二个 then 函数才会执行。

2.2、catch()

除了 then 函数外,在 Promise 原型上还有一个 catch 函数,此函数时当 Promise 内部异步出现错误的时候即为 rejected 状态时,才执行。

1
2
3
4
5
6
7
new Promise((resolve, reject)=>{
throw new Error('test')
}).catch(err=>console.log(err)) // Error: test
等同于:
new Promise((resolve, reject)=>{
throw new Error('test')
}).then(null, err=>console.log(err)) // Error: test

当 then 第二个参数和 catch 函数同时存在时,将不会执行 catch 函数:

1
2
3
4
5
new Promise((resolve, reject) => {
throw new Error("test");
})
.then(null, (err) => console.log(err)) // Error: test
.catch((err) => console.log(err));

那此时的 catch 捕获的是哪个 Promise 的错误呢?捕获的是前一个 Promise 的错误,即 then 函数返回回来的 Promise 错误:

1
2
3
4
5
6
7
new Promise((resolve, reject) => {
throw new Error("test");
})
.then(null, (err) => {
throw new Error("test1");
})
.catch((err) => console.log(err)); // Error: test1

Promise 的错误有一种类似冒泡机制,当 catch 之前没有没有任何函数截获错误,那终究会被 catch 截获。

1
2
3
4
5
new Promise((resolve, reject) => {
throw new Error("test");
})
.then()
.catch((err) => console.log(err)); // Error: test

只要 catch 前任何一个 Promise 报错,那终究会被 catch 截获。
通常情况下,不建议使用 then 函数的第二个参数来进行错误的捕获,如上所说的 catch 写法可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用 catch()方法,而不使用 then()方法的第二个参数。
then()返回一个新的 Promise 对象,catch()同样返回一个 Promise 对象,同样可以使用链式调用:

1
2
3
4
5
6
new Promise((resolve, reject) => {
throw new Error("test");
})
.then()
.catch((err) => console.log(err)) // Error: test
.then(() => console.log(2)); // 2

当 catch 捕获完错误后,会接着执行下面的 then 方法,当没有错误抛出时,则会跳过 catch,直接执行后面的 then 方法。但是之后的 Promise 出现错误时,之前的 catch 就捕获不到了。

1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
throw new Error("test");
})
.then()
.catch((err) => {
throw new Error("test1");
}) // Error: test
.then(() => console.log(2));

catch 后面可以链式调用 then 方法,同样也可以调用 catch 方法,后面的 catch 方法是接收前一个 catch 方法所抛出的错误。

2.3、finally

在 ES8 中新加入了一个方法,即 finally,此方法不同于 then 和 catch,它不跟踪与 Promise 对象状态的改变,即不管 Promise 的最终状态如何,都会执行这个方法,同时 finally 不同于 then 和 catch 地方就是,它不接受任何参数。

1
2
3
4
5
6
7
8
9
10
11
new Promise((resolve) => {
resolve(1);
})
.then((value) => console.log(value)) // 1
.finally(() => console.log(2)); // 2

new Promise(() => {
throw new Error("test");
})
.catch((err) => console.log(err)) // Error test
.finally(() => console.log(2)); // 2

finally 同样返回一个新的 Promise 对象,用法和之前的 then 和 catch 一样,这块就不做过多的讲解了。

3、其他 API

除了上述 Promise 原型上的方法外,Promise 还有很多其他的 API。

3.1、 Promise.all

通过字面意思就能看出来,这个方法是‘全部’意思,由此可见可以接受多个 Promise 对象。
该方法接受具有 Iterator 接口并且每个成员都是 Promise 实例的参数,并返回一个新的 Promise 对象。

1
2
3
4
5
6
7
8
9
10
11
let p = Promise.all([
new Promise((resolve) => {
resolve(1);
}),
new Promise((resolve) => {
resolve(2);
}),
new Promise((resolve) => {
resolve(3);
}),
]);

并且,只有当接受的参数中所有成员的状态都为 resolved 的时候,p 的状态才会为 resolved,如果有一个成员的状态为 rejected,那 p 的状态就为 rejected。
当所有成员的状态均为 resolved 的时候,会将每个成员 resolved 状态下的值拼成数组传递给 p 的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
let p = Promise.all([
new Promise((resolve) => {
resolve(1);
}),
new Promise((resolve) => {
resolve(2);
}),
new Promise((resolve) => {
resolve(3);
}),
]);
p.then((result) => console.log(result)); // [1, 2, 3]

当有一个成员的状态为 rejected 的时候,则会将第一个 rejected 状态的值返给 p 的 catch 方法。

1
2
3
4
5
6
7
8
9
10
11
12
let p = Promise.all([
new Promise((resolve) => {
resolve(1);
}),
new Promise((resolve, rejecct) => {
rejecct(2);
}),
new Promise((resolve) => {
resolve(3);
}),
]);
p.catch((err) => console.log(err));

如果有一个成员为 rejected 状态,并且自身调用了 catch 方法,那将不会走 p 对象的 catch 方法,这一点要注意。

3.2、 Promise.race

race 翻译成中文是竞赛的意思,他表示多个 Promise 对象,哪个成员率先改变状态,那 Promise.race 返回的 Promise 对象的状态变为什么,并将值转递给 p 的回调函数,它和 Promise.all 接收的参数一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let p = Promise.race([
new Promise((resolve) => {
setTimeout(() => resolve(1), 100);
}),
new Promise((resolve) => {
setTimeout(() => resolve(2), 50);
}),
new Promise((resolve) => {
setTimeout(() => resolve(3), 200);
}),
]);
p.then((result) => console.log(result)); // 2

let p = Promise.race([
new Promise((resolve) => {
setTimeout(() => resolve(1), 100);
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(2), 50);
}),
new Promise((resolve) => {
setTimeout(() => resolve(3), 200);
}),
]);
p.catch((err) => console.log(err)); // 2

3.3、Promise.allSettled

该方法是 ES2020 新加入的,和 all 一样,返回一个新的 Promise 对象,接收一组 Promise 对象,但是和 all 区别的是,当不管每个成员的 Promise 是什么状态,只要执行结束,则返回的 Promise 对象就会执行结束。

1
2
3
4
5
6
7
8
9
10
11
12
let p = Promise.allSettled([
new Promise((resolve) => {
resolve(1);
}),
new Promise((resolve, rejecct) => {
rejecct(2);
}),
new Promise((resolve) => {
resolve(3);
}),
]);
p.then((value) => console.log(JSON.stringify(value))); // [{status:"fulfilled",value:1},{status:"rejected",reason:2},{status:"fulfilled",value:3}]

有时候异步请求并不在意是否能够成功,这个时候这个方法就很符合场景了,并且返回一个数组,数组中每个对象有两个状态,一个是 fulfilled,另一个是 rejected,返回之后可以进行筛选,查看错误信息。

3.4、Promise.resolve

将一个对象转化为一个 Promise 对象。

1
2
3
Promise.resolve("foo");
等价于;
new Promise((resolve) => resolve("foo"));

当 Promise.resolve 的参数是一个 Promise 实例时,原封不动的返回这个实例:

1
2
3
let p = new Promsie((resolve) => resolve(1));
let p1 = Promise.resolve(p);
p === p1; // true

当参数是一个 thenable 对象时,即含有 then 方法的对象时,会返回一个 Promise 对象,并立即执行 then 方法。

1
2
3
4
5
6
7
let thenable = {
then: function (resolve, reject) {
resolve(1);
},
};
let p = Promise.resolve(thenable);
p.then((value) => console.log(value)); // 1

当参数是不是一个 thenable 对象时,由于参数不是一个异步的,所以当 Promise.resolve 后,直接的状态就是 resolved 的状态,所以 then 后就会输出原值。

1
2
3
4
5
6
7
8
9
10
1. 参数是个普通对象
let obj = {
name: '1'
}
let p = Promise.resolve(obj);
p.then(value=>console.log(value)) // {name: '1'}
2. 参数是个基本数据类型
let num = '1';
let p = Promise.resolve(num);
p.then(value=>console.log(value)) // 1

当不传参数的时候,返回的就是一个带有 resolved 状态的 Promise 对象。

3.5、Promise.reject

返回一个状态为 rejected 的 Promise 对象,传入的参数作为错误信息作为后续方法传递的参数。

1
2
3
let num = "1";
let p = Promise.reject(num);
p.then(null, (err) => console.log(err)); // 1

当参数是 thenable 对象时,返回的不是 error 信息而是 thenable 对象。

1
2
3
4
5
6
7
let thenable = {
then: function (resolve, reject) {
reject(1);
},
};
let p = Promise.reject(thenable);
p.then(null, (err) => console.log(err === thenable)); // true