ES6 中 Promise
文章内容参考《ECMAScript 6 入门》。
什么是 Promise?
Promise 是一种比传统的 回调函数和事件调用 更加合理的异步编程的一种解决方案。
Promise 对象可以获取异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作)的结果。
Promise 可解决的问题:
- 解决回调地狱的问题
- 支持多个并发的请求,获取并发请求返回的数据。
Promise 的缺点:
- 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
回调地狱
异步操作时,在请求成功的回调函数里继续写函数,或者继续进行异步操作,层层嵌套,就会形成回调地狱。
回调地狱会让我们的代码看起来很糟糕,难以维护,且性能低下。
Promise 的特点
- Promise 有三种状态:
- pending(未完成)
- fulfilled(已成功)
- rejected(已失败)
- Promise 对象的状态改变,只有两种可能:
- 从 pending(未完成)变为 fulfilled(已成功)
- 从 pending(未完成)变为 rejected(已失败)
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
基本用法
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。
Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。
1 | let promise = new Promise(function (resolve, reject) { |
catch 方法相当于 then 方法的第二个参数,即失败 reject 的回调。
1 | promise |
Promise 新建后就会立即执行。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20console.log(1)
let promise = new Promise(function (resolve, reject) {
console.log(2)
resolve(3)
console.log(4)
})
// then的第二个函数是可选的,非必须。
promise.then(function (value) {
console.log(value)
})
console.log(5)
// 1
// 2
// 4
// 5
// 3
上面代码中注意三点:
Promise 新建后会立马执行其中代码
调用 resolve 或 reject 并不会终结 Promise 的参数函数的执行
在当前脚本所有同步任务执行完后才会执行 then 方法指定的回调函数
then() 的链式调用
then 方法是定义在原型对象 Promise.prototype 上的。
调用 then 方法后返回的是一个新的 Promise 实例,因此可以继续调用 then 方法,实现链式调用。
1 | new Promise((resolve, reject)=> { |
上面代码中,新建了一个 Promise 实例,实例内用 resolve 函数修改 Promise 对象状态从 “未完成” 变为 “成功” 并传递参数数字 1,第一个 then 方法的回调函数执行内容,完成后返回了数字 2 ,参数传递给了第二个 then 的回调函数。
在 then 方法中可以返回一个 Promise 对象(即有异步操作),这时后一个 then 方法的回调函数,会等待返回的 Promise 对象的状态发生变化,才会被调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function getNum (num) {
let promise = new Promise((resolve, reject)=> {
setTimeout(() => {
resolve(num)
}, 1000)
})
return promise
}
getNum(1)
.then(val => {
console.log(val)
return getNum(2) // 返回Promise对象
})
.then(val => {
console.log(val)
})
// 1
// 2
上面代码中,第一个 then 方法的回调函数执行完成后返回了一个 Promise 对象,一秒后,返回的 Promise 对象状态从 “未完成” 变为 “成功” ,此时开始执行第二个 then 方法里的回调函数。
catch() 方法详解
跟 then 方法一样,catch 方法定义在原型对象 Promise.prototype 上的。
1 | let promise = new Promise((resolve, reject)=> { |
上面代码中,新建了一个 Promise 实例,实例内用 reject 函数,此时 Promise 对象状态从 “未完成” 变为 “失败” ,就调用 catch 方法指定的回调函数,处理这个错误。
接下来通过一个例子说明以下特性:
- 如果 Promise 状态已经变成resolved,再抛出错误是无效的。
- then 方法指定的回调函数,如果运行中出错,也会被catch方法捕捉到。
- Promise 对象抛出的错误,总是会被下一个catch语句捕获。
- Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
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
26
27
28
29
30
31
32let promise = new Promise((resolve, reject)=> { // 解析一
resolve('Promise实例正确')
throw new Error('Promise实例出错');
})
promise
.then(val => { // 解析二
console.log(val)
throw new Error('then方法内出错')
return '链式调用then'
})
.then(val => { // 解析三
console.log(val)
})
.catch(err => { // 解析四
console.log(err)
throw new Error('catch方法内出错')
})
.catch(err => { // 解析五
console.log(err)
throw new Error('最后的catch方法出错')
})
setTimeout(() => { // 解析六
console.log('promise后调用')
}, 0)
// Promise实例正确
// Error: then方法内出错
// Error: catch方法内出错
// Uncaught (in promise) Error: 最后的catch方法出错
// promise后调用
根据上述代码块上注释的解析步骤,分析一下:
步骤一
- 构建了一个 Promise 对象,内部我们通过 resolve 函数修改状态为 “成功”。
- 创建了一个错误对象。
- 注意:Promise 对象状态已经变成 resolved,并不会二次修改状态,即第二步抛出的错误不会被捕捉。
步骤二
- 因为 Promise 对象状态为 “成功”,所以执行 then 方法第一个函数,打印了调用 resolve 函数传来的值。
- 创建了一个错误对象,修改 Promise 对象状态变为 “失败”。
- 返回字符串 ‘链式调用then’ 。
- 注意:(1) 此时状态已经变成 rejected。 (2) then 方法指定的回调函数抛出错误,也会被 catch 方法捕获。
步骤三
- 第一个 then 指定的回调函数抛出错误,即返回的 Promise 对象状态为 ‘失败’。
- 当前 then 不执行,直接执行下方最近的 catch 方法。
步骤四
- 捕捉到第一个 then 方法抛出的错误,打印错误内容。
- 创建了一个错误对象
步骤五
- 捕捉到第四步中 catch 方法内抛出的错误,打印错误内容。
- 创建了一个错误对象
- 因为之后没有 catch 方法了,于是浏览器打印了报错信息。
- 注意:Promise 对象内抛出的错误,不会退出进程、终止脚本执行。
步骤六
- 打印字符串 ‘promise后调用’。
finally() 方法
finally 方法不管 Promise 对象状态是什么,都会执行。
1 | promise.finally(() => { |
上面代码中,如果不使用 finally 方法,同样的语句需要为成功和失败两种情况各写一次。有了 finally 方法,则只需要写一次。
finally 方法的回调函数不接受任何参数,且会返回原来的值。1
2
3
4
5
6
7
8
9
10new Promise((resolve, reject) => {
resolve(1)
})
.finally(() => {
console.log(2)
})
.then(val => console.log(val))
// 2
// 1
从上面的实现还可以看到,finally 方法总是会返回原来的值。
finally 方法的实现也很简单。1
2
3
4
5
6
7Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
all() 方法
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。
1 | function Apple () { |
上面代码中,Promise.all() 方法接受一个数组作为参数,数组内每项都是 Promise 实例。fruits 的状态由 apple、peach 决定,分成两种情况。
(1)只有 apple、peach 的状态都变成 fulfilled,fruits 的状态才会变成 fulfilled,此时 apple、peach 的返回值组成一个数组,传递给 fruits 的回调函数。
(2)只要 apple、peach 之中有一个被rejected,fruits的状态就变成 rejected,此时第一个被 reject的实例的返回值,会传递给 fruits的回调函数。
下面是一个具体的例子。
1 | let promise1 = new Promise((resolve, reject) => { |
上面代码中,promise2 被 rejected, p 就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p的回调函数。
注意,如果作为参数的 Promise 实例,自己定义了 catch方法,那么它一旦被rejected,并不会触发 Promise.all()的 catch 方法。
1 | let promise1 = new Promise((resolve, reject) => { |
上面代码中,promise2 会 rejected,但是 promise2 有自己的 catch方法,该方法返回的是一个新的 Promise 实例,promise2 指向的实际上是这个实例。该实例执行完 catch 方法后,也会变成 resolved,导致 Promise.all()方法参数里面的两个实例都会变 resolved。
如果 promise2没有自己的 catch方法,就会调用 Promise.all() 的 catch方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let promise1 = new Promise((resolve, reject) => {
resolve(1)
}).then(val => val)
let promise2 = new Promise((resolve, reject) => {
throw new Error('出错了')
})
let promise3 = new Promise((resolve, reject) => {
resolve(3)
}).then(val => console.log(val))
let p = Promise.all([promise1, promise2, promise3])
p.then(val => {
console.log(val)
}).catch(err => {
console.log('捕捉到的出错:”' + err + '”')
})
// 3
// 捕捉到的出错:”Error: 出错了”
END
无论怎样,都别丢了快乐和努力。
转载分享,请标明出处。