ES6 Promise深入学习

定义

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。

描述

Promise 能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来,这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

特点

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

Promise.prototype.thenPromise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。

image-20210107144930322

应用场景

在实际开发中,我们经常遇到接口A需要的参数依赖于接口B,亦或是执行完一个异步操作后,再做另一个操作。首先看一道经常遇到的面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(a, b) {
console.log(2);
for(var i = 0; i < 10; i++) {
i == 9 && a();
}
console.log(3);
}).then(function() {
console.log(4)
});
console.log(5)

2 3 5 4 1

如果你做对了,那可能你对Javescript的执行顺序还是有一点了解的。

我们简单把一个setTimeout当做一个ajax异步操作,像下面这种场景是很常见的,如果更多的这种依赖关系,,对于代码是场灾难,造成回调地狱。

1
2
3
4
5
6
setTimeout(() => {
console.log('我删除了表格第一行')
setTimeout(() => {
console.log('我刷新了表格')
}, 1000)
}, 1000)

而promise的链式调用可以帮我们解决这种问题,避免层层嵌套。例如:

1
2
3
4
5
6
7
8
new Promise(function(resolve) {
setTimeout(() => {
console.log('我删除了表格的第一行')
resolve()
}, 1000)
}).then(function() {
console.log('我重新刷新了表格')
});

优点

  • 将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
  • Promise 对象提供统一的接口,使得控制异步操作更加容易

缺点

  • 无法取消 Promise,一旦新建它就会立即执行,无法中途取消
  • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
  • 处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

tips:Pending 状态不可逆

原型方法

Promise.prototype.then()

定义

then()方法返回一个Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

语法
1
2
3
4
5
6
7
p.then(onFulfilled[, onRejected]);

p.then((value) => {
// fulfillment
}, reason => {
// rejection
});
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
promise2 = new Promise((resolve, reject) => {
resolve('Success') // 会执行第一个函数
reject('Error!'); // 会执行第二个函数
// 此处resolve,reject都执行的话,实际只执行了resolve,因为pending状态不可逆,在前面已经被转为fulfilled状态
});

promise2.then((res) => {
console.log('执行了第一个函数');
// expected output: "Success!"
}, (e) => {
console.log('执行了第二个函数');
});

案例1

image-20210107162549241

案例2

image-20210107164656213

你可以传递一个匿名函数给 then,并且,如果它返回一个 Promise,一个等价的 Promise 将暴露给后续的方法链。上面的代码片段使用 setTimout 函数来模拟异步代码操作。

案例三

image-20210107165256834

如果函数抛出错误或返回一个拒绝的Promise,则 then 将返回一个拒绝的Promise。

Promise.prototype.catch()

定义

catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用*Promise.prototype.then(undefined, onRejected)* 相同。

语法
1
2
3
4
5
p.catch(onRejected);

p.catch(function(reason) {
// 拒绝
});
使用
1
2
3
4
5
6
7
8
9
promise2 = new Promise((resolve,reject) => {
reject('Error!');
});

promise2.then((res) => {
// 不会进这里
}).catch(e => {
console.log(e) // Error!
});
案例一
1
2
3
4
5
6
7
8
9
10
11
var p2 = new Promise(function(resolve, reject) {
// resolve(); 若先执行这个,再执行后面的抛错,也不会被捕获
// throw 'Uh-oh!'; 可以捕获
setTimeout(function() {
throw 'Uncaught Exception!'; // 不被捕获
}, 1000);
});

p2.catch(function(e) {
console.log(e); // 不会执行
});

Promise.prototype.finally()

定义

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这避免了同样的语句需要在then和catch中各写一次的情况。

用法
1
2
3
4
5
p.finally(onFinally);

p.finally(function() {
// 返回状态为(resolved 或 rejected)
});

静态方法

Promise.resolve()

定义

Promise.resolve(value)方法返回一个以给定值解析后的Promise对象

语法
1
Promise.resolve(value);
使用
1
2
3
4
5
6
const promise1 = Promise.resolve(123);

promise1.then((value) => {
console.log(value);
// expected output: 123
});
案例一

如果resolve的value是普通变量值,promise.then(res) 的参数就是上面接收的变量value。

如果resolve的value是另一个promise对象,则promise.then(res) 的参数就是第一次resolve的变量。

如果resolve的value是then对象,则promise.then(res) 的参数就是then对象resolve的值。

image-20210107171854030

image-20210107171824364

Promise.reject()

定义

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

语法
1
Promise.reject(reason);
用法
1
2
3
4
5
Promise.reject(new Error('fail')).then(function() {
// not called
}, function(error) {
console.error(error);
});

Promise.all()

定义

Promise.all() 方法接收一个promise的iterable类型的输入,并且只返回一个Promise实例

语法
1
Promise.all(iterable);
使用
1
2
3
4
5
6
7
8
9
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});

Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
案例一

image-20210107174454242

传入的可迭代对象不为空时异步执行,传入的可迭代对象为空时同步执行

案例二

image-20210107174806210

失败时也是一样

Promise.race()

定义

Promise.race(iterable) 方法返回一个 Promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

语法
1
Promise.race(iterable);
用法

这个没啥好说的,用法跟Promise.all差不多,区别是Promise.all所有可迭代对象执行完成再执行,Promise.race有一个被resolve或reject即执行。

Promise.allSettled()

定义

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果

用法案例

image-20210107180653190

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。