ES6中的Promise详解

ES6 中 Promise

文章内容参考《ECMAScript 6 入门》。

什么是 Promise?

Promise 是一种比传统的 回调函数和事件调用 更加合理的异步编程的一种解决方案。

Promise 对象可以获取异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作)的结果。

Promise 可解决的问题:

  1. 解决回调地狱的问题
  2. 支持多个并发的请求,获取并发请求返回的数据。

Promise 的缺点:

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

回调地狱

异步操作时,在请求成功的回调函数里继续写函数,或者继续进行异步操作,层层嵌套,就会形成回调地狱。

回调地狱会让我们的代码看起来很糟糕,难以维护,且性能低下。

Promise 的特点

  • Promise 有三种状态:
  1. pending(未完成)
  2. fulfilled(已成功)
  3. rejected(已失败)
  • Promise 对象的状态改变,只有两种可能:
  1. pending(未完成)变为 fulfilled(已成功)
  2. pending(未完成)变为 rejected(已失败)
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果

基本用法

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolvereject

Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let promise = new Promise(function (resolve, reject) {
if (/* 异步操作成功 */) {
// resolve函数将 Promise对象的状态从“未完成”变为“成功”
resolve(value);
} else {
// reject函数将 Promise对象的状态从“未完成”变为“失败”
reject(error);
}
});


// then 方法有两个参数,第一个是成功 resolve 的回调,第二个是失败 reject 的回调
promise.then(
// Promise对象状态为“成功”时执行
function (value) {

},
// Promise对象状态为“失败”时执行
function (error) {

}
)

catch 方法相当于 then 方法的第二个参数,即失败 reject 的回调。

1
2
3
4
5
6
7
promise
.then( // Promise对象状态为“成功”时执行
function (value) {}
)
.catch( // Promise对象状态为“失败”时执行
function (error) {}
)

Promise 新建后就会立即执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.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

上面代码中注意三点:

  1. Promise 新建后会立马执行其中代码

  2. 调用 resolvereject 并不会终结 Promise 的参数函数的执行

  3. 在当前脚本所有同步任务执行完后才会执行 then 方法指定的回调函数

then() 的链式调用

then 方法是定义在原型对象 Promise.prototype 上的。

调用 then 方法后返回的是一个新的 Promise 实例,因此可以继续调用 then 方法,实现链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
new Promise((resolve, reject)=> {
resolve(1)
})
.then(val => { // 状态为成功时执行
console.log(val)
return 2
})
.then(value => { // 状态为成功时执行
console.log(value)
})

// 1
// 2

上面代码中,新建了一个 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
20
function 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
2
3
4
5
6
7
8
9
10
11
12
13
14
let promise = new Promise((resolve, reject)=> {
reject('出错啦')
})

promise
.then(val => console.log(val))
.catch(err => console.log(err))
// 等同于
promise
.then(val => console.log(val))
.then(null, err => console.log(err))

// 出错啦
// 出错啦

上面代码中,新建了一个 Promise 实例,实例内用 reject 函数,此时 Promise 对象状态从 “未完成” 变为 “失败” ,就调用 catch 方法指定的回调函数,处理这个错误。

接下来通过一个例子说明以下特性:

  1. 如果 Promise 状态已经变成resolved,再抛出错误是无效的。
  2. then 方法指定的回调函数,如果运行中出错,也会被catch方法捕捉到。
  3. Promise 对象抛出的错误,总是会被下一个catch语句捕获。
  4. 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
    32
    let 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后调用

根据上述代码块上注释的解析步骤,分析一下:

步骤一

  1. 构建了一个 Promise 对象,内部我们通过 resolve 函数修改状态为 “成功”
  2. 创建了一个错误对象。
  3. 注意:Promise 对象状态已经变成 resolved,并不会二次修改状态,即第二步抛出的错误不会被捕捉。

步骤二

  1. 因为 Promise 对象状态为 “成功”,所以执行 then 方法第一个函数,打印了调用 resolve 函数传来的值。
  2. 创建了一个错误对象,修改 Promise 对象状态变为 “失败”
  3. 返回字符串 ‘链式调用then’ 。
  4. 注意:(1) 此时状态已经变成 rejected。 (2) then 方法指定的回调函数抛出错误,也会被 catch 方法捕获。

步骤三

  1. 第一个 then 指定的回调函数抛出错误,即返回的 Promise 对象状态为 ‘失败’
  2. 当前 then 不执行,直接执行下方最近的 catch 方法。

步骤四

  1. 捕捉到第一个 then 方法抛出的错误,打印错误内容。
  2. 创建了一个错误对象

步骤五

  1. 捕捉到第四步中 catch 方法内抛出的错误,打印错误内容。
  2. 创建了一个错误对象
  3. 因为之后没有 catch 方法了,于是浏览器打印了报错信息。
  4. 注意:Promise 对象内抛出的错误,不会退出进程、终止脚本执行。

步骤六

  1. 打印字符串 ‘promise后调用’。

finally() 方法

finally 方法不管 Promise 对象状态是什么,都会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
promise.finally(() => {
// 语句
})

// 等同于
promise.then(
result => {
// 语句
return result
},
error => {
// 语句
throw error
}
)

上面代码中,如果不使用 finally 方法,同样的语句需要为成功和失败两种情况各写一次。有了 finally 方法,则只需要写一次。

finally 方法的回调函数不接受任何参数,且会返回原来的值。

1
2
3
4
5
6
7
8
9
10
new Promise((resolve, reject) => {
resolve(1)
})
.finally(() => {
console.log(2)
})
.then(val => console.log(val))

// 2
// 1

从上面的实现还可以看到,finally 方法总是会返回原来的值。

finally 方法的实现也很简单。

1
2
3
4
5
6
7
Promise.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 实例。

Promiseall 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

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
32
33
34
35
36
37
function Apple () {
console.log('看到苹果')
let promise = new Promise((resolve, reject) => {
// 做一些异步操作
setTimeout(function () {
console.log('买苹果')
resolve('吃苹果')
}, 1000)
})
return promise
}

function Peach () {
console.log('看到桃子')
let promise = new Promise((resolve, reject) => {
// 做一些异步操作
setTimeout(function () {
console.log('买桃子')
resolve('吃桃子')
}, 1000)
})
return promise
}

let apple = Apple()
let peach = Peach()

let fruits = Promise.all([apple, peach])
fruits.then(arr => {
console.log(arr)
})

// 看到苹果
// 看到桃子
// 买苹果
// 买桃子
// ["吃苹果", "吃桃子"]

上面代码中,Promise.all() 方法接受一个数组作为参数,数组内每项都是 Promise 实例。fruits 的状态由 applepeach 决定,分成两种情况。

(1)只有 applepeach 的状态都变成 fulfilledfruits 的状态才会变成 fulfilled,此时 applepeach 的返回值组成一个数组,传递给 fruits 的回调函数。

(2)只要 applepeach 之中有一个被rejected,fruits的状态就变成 rejected,此时第一个被 reject的实例的返回值,会传递给 fruits的回调函数。

下面是一个具体的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let promise1 = new Promise((resolve, reject) => {
resolve(1)
})

let promise2 = new Promise((resolve, reject) => {
throw new Error('出错了')
})

let promise3 = new Promise((resolve, reject) => {
reject('又出错了')
})

let p = Promise.all([promise1, promise2, promise3])

p.then(val => {
console.log(val)
}).catch(err => {
console.log('捕捉到的出错:”' + err + '”')
})

// 捕捉到的出错:”Error: 出错了”

上面代码中,promise2rejected, p 就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p的回调函数。

注意,如果作为参数的 Promise 实例,自己定义了 catch方法,那么它一旦被rejected,并不会触发 Promise.all()catch 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let promise1 = new Promise((resolve, reject) => {
resolve(1)
}).then(val => val)

let promise2 = new Promise((resolve, reject) => {
throw new Error('出错了')
}).catch(err => err)

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
// [1, Error: 出错了, undefined]

上面代码中,promise2rejected,但是 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
22
let 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

无论怎样,都别丢了快乐和努力。

转载分享,请标明出处。



喜欢可以支持一下