JavaScript中的Promise
1. Promise 的介绍
Promise 是异步编程的一种新的解决方案,从早期的回调函数
、事件
相比,更加合理和强大。语法上来说,Promise
是一个对象,可以获取异步操作成功或是失败的结果。
Promise 有三种状态:pending (进行中)
,fulfilled (resolved 已成功)
,rejecred (已失败)
。只有当异步操作的结果可以决定当前是哪一种状态,任意其他操作都无法改变这个状态。
一旦状态发生改变,就不会再变,任意时刻都可以获取到这个结果。Promise 对象
的状态改变只有两种情况:从pending (进行中)
改变为resolved (已成功)
,从pending (进行中)
改变为rejected (已失败)
。
1.1 Promise 的优缺点
而通过 Promise 对象
就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
当然,Promise
也有一些缺点,例如:
- 无法取消
Promise
,一旦新建就会立即执行,无法中途取消 - 如果不设置回调函数。
Promise
抛出的错误不会反应到外界 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段 ( 是刚刚开始执行还是即将完成操作 )
2. Promise 的基本用法
2.1 生成实例
ES6规定,Promise
对象是一个构造函数,用来生成 Promise
实例。
// 这就简单的创建了一个 Promise 实例
new Promise((resolve, reject) => {
if(/* 异步操作是否成功 */) {
resolve(value)
}else {
reject(error)
}
})
上面的代码中,可以看到 Promise 构造函数
接受了一个函数作为参数,这个函数有携带两个参数: resolve
、reject
。这两个参数都是函数,由 JavaScript 引擎
提供,不需要自己传参。
resolve
:将Promise
对象的状态改变为:resolved
(已成功),可以在异步操作完成后进行执行,并且可以将异步操作的结果作为参数传递出去。reject
:将Promise
对象的状态改变为:rejected
(已失败),同样在异步操作完成后进行执行,同样可以将异步操作的结果作为参数传递出去。
2.2 使用 then() 指定回调函数
在 Promise
实例生成后,可以使用 then()
方法分别指定 resolved
状态和 rejected
状态的回调函数
then()
方法接受两个函数作为参数,其中,第一个作为 resolved
的回调函数,第二个作为 rejected
的回调函数,两个函数都是可选的
,不一定非要提供。
new Promise((resolve, reject) => {
// reject('拒绝状态')
resolve('成功状态')
})
.then(
(value) => {
console.log('成功的业务处理')
},
(reason) => {
console.log('拒绝的业务处理')
}
)
// 简单的伪装为一个网络请求
let statusCode = 404 // 假设这是请求返回的状态码
// 实例化 Promise 对象
const p = new Promise(function (resolve, reject) {
// 成功
if ( statusCode >= 200 && statusCode < 300 ) {
let data = '数据库中的用户数据'
resolve(data)
}
// 失败
else if (statusCode === 404) {
let err = '获取数据失败'
reject(err)
}
})
// 调用 promise 对象的 then 方法
p.then(
function (value) {
console.log(value)
},
function (reason) {
console.log(reason)
}
)
2.3 链式调用 then()
Promise.then()
其实也一个 Promise
,同样会返回状态。
只是没有设置的情况的默认返回的就是 resolved
状态,且不会携带任何值。
哪怕手动返回了一个 非Promise类型
的值,也会被自动转换为 Promise.resolve()
的参数。
// 创建一个 Promise 实例,修改状态为 reject
let p1 = new Promise((resolve, reject) => {
reject('拒绝')
})
let p2 = p1
.then(
(value) => {
console.log(value)
},
(error) => {
console.log(error)
// 如果这里不手动返回一个 Promise 的 reject 状态,下一个 then 就会执行 resolve 状态的回调函数
// return new Promise((resolve, reject)=>{
// reject('失败')
// })
}
)
.then(
(value) => {
console.log('成功')
},
(error) => {
console.log(error)
}
)
// 输出结果为:
// 拒绝
// 成功
3. Promise.resolve() 和 Promise.reject()
Promise.resolve()
和 Promise.reject()
可以将一个现有对象转换为 Promise
对象。
Promise.resolve()
Promise.reject()
// 就相当于
new Promise(resolve => resolve())
new Promise((resolve,reject) => reject())
4. Promise.prototype.catch()
在上面的例子中,虽然可以使用 then()
里面的第二个函数参数作为发送错误的回调函数;
但是如果只需要失败的回调,除了可以将第一个参数忽略;还可以使用 catch()
方法,catch()
方法不但可以捕获 Promise
实例抛出的错误,then()
运行时发出的错误同样可以捕获。
// 正常情况下,如果只需要失败的回调,可以将第一个参数设置为 null 或是 undefined 即可
new Promise((resolve, reject) => {
reject('已失败')
}).then(null, error => {
console.log(error)
})
// 但是上面代码完全可以使用 catch() 进行优化,效果是一样的,但是增加了可读性
new Promise((resolve, reject) => {
// reject('已失败')
throw new Error('请求超时')
}).catch(error => {
console.log(error)
})
4.1 多个 Promise 对象
Promise 的错误具有 “冒泡” 性质,会一直往后传递,知道被捕获为止。
new Promise(resolve => {
resolve('ok')
})
.then((value) => {
// 手动返回一个 rejected 状态
return new Promise((resolve, reject) => {
reject('P2')
})
})
.then((value) => {
console.log(value)
})
// 如果上面的 then 都没有设置第二参数 ( rejected 的回调函数 ), 就会'冒泡'到最后的 catch() 进行执行
.catch((error) => {
console.log(error)
// p2
})
5. Promise.all()
Promise.all() 方法可以一个数组作为参数,其中数组中的元素都是 Promise 实例,如果不是,会自动执行 Promise.resolve() 转换为一个 Promise 实例,再进一步处理。
Promise.all() 提供了并行执行异步操作的能力,会在所有的异步操作执行完毕且状态全为 resolved 才会将自身状态修改为 resolved, 并且把所有 Promise 实例的返回值打包为一个数组。
只要有一个 Promise 实例的状态变为 rejected, Promise.all() 就会直接变为 rejected,并且接收第一个 rejected 实例的返回值作为参数传递出去。
const promise_1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('第一个异步请求成功了')
reject('第一个异步请求失败了')
}, 1000)
})
const promise_2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('第二个异步请求成功了')
}, 1000)
})
// 以第一个被 reject 的实例的返回值作为 Promose.all 的回到函数的参数
Promise.all([promise_1, promise_2]).then((value) => {
console.log(value)
// 第一个异步请求失败了
// 如果第一个 Promise 实例为成功则返回:['第一个异步请求成功了', '第二个异步请求成功了']
})
5.1 如果 Promise 实例自己捕获了错误呢?
上文说到,如果捕获到 rejected 状态就会直接返回给 Promise.all() 方法,那么作为参数的 Promise 实例身上就自己定义了 catch() 呢?如果定义了,那么就不会触发 Promise.all() 的 catch() 方法。
因为 Promise.catch() 方法返回的也是一个 Promise, 但由于没有主动定义返回状态,所以返回到 Promise.all() 这里自然就是 resolved 状态了,且参数为空。
const promise_1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('第一个异步请求失败了')
}, 1000)
}).catch((error) => {
console.log(error)
// 第一个异步请求失败了
})
const promise_2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('第二个异步请求')
}, 1000)
})
Promise.all([promise_1, promise_2]).then((value) => {
console.log(value)
// [ undefined, '第二个异步请求']
})
6. Promise.allSettled()
上面学习的 Promise.all()
方法,需要所有实例返回 resolved 状态
才算成功,只要由一个状态为 rejected 状态
就会直接修改状态为 rejected 状态
,无论其他操作是否完成。
为了解决这个问题,在 ES11 中新增了 Promise.allSettled()
方法,用来确定一组 Promise
是否都完成了 ( 无论成功与否 )。
同样的,Promise.allSettled()
也是接收一个 Promise
实例组成的数组作为参数,并且返回一个新的 Promise
。
只是不同的是,Promise.allSettled()
方法只有等到所有的 promise
实例的状态都改变时
,返回的 Promise
的状态才会改变。
// 虽然有一个作为参数的 Promise 实例状态为 rejected,但依然返回了数据
const p1 = new Promise((resolve, reject) => {
reject('失败')
})
const p2 = new Promise((resolve, reject) => {
resolve('成功')
})
Promise.allSettled([p1, p2]).then((value) => {
console.log(value)
// [
// {status: 'rejected', reason: '失败'},
// {status: 'fulfilled', value: '成功'}
// ]
})
7. Promise.prototype.race()
Promise.race()
同样是将多个 Promise
实例,包装成一个新的 Promise
实例。
Promise.all()
需要等作为参数所有的 Promise
实例执行完成才会改变状态; 而 Promise.race()
是当作为参数的 Promise
实例有一个先改变状态时
( 无论成功与否 ), 直接返回给 Promise.race()
了。
7.1 网络请求超时的案例
// p1 为一个网络请求
let p1 = new Promise((resolve, reject) => {
if(/*请求是否成功*/) {
resolve()
}else {
reject('请求失败')
}
})
// p2 为一个 2s 的定时器
let p2 = new Promise((resolve, reject) => {
setTimeout(()=>{
reject('请求超时')
}, 2000)
})
// 执行 Promise.race() 方法,只要请求时间超过 2s,就会直接返回请求超时的错误
Promise.race([p1, p2])
.then((value) => {
console.log(value)
})
.catch((error) => {
console.log(error)
})
8. Promise.prototype.finally
Promise.finally()
方法是 ES2018 引入的,用于指定不管 Promise 对象最后状态如何,都会执行的操作
。
// 封装的 Ajax 请求
function ajax(url) {
return new Promise((resolve, reject) => {
// 显示 loading 动画元素
loading.style.display = 'block'
let xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.send()
xhr.onload = function () {
if (this.status == 200) {
resolve(JSON.parse(this.response))
} else {
reject('加载失败')
}
}
xhr.onerror = function () {
reject(this)
}
})
}
// 请求的 url 地址
let src = 'https://api.apiopen.top/api/sentences'
ajax(src)
.then((value) => {
//请求成功打印获取的数据
console.log(value)
})
.finally(() => {
// 无论成功与否都要隐藏 loading 动画元素
loading.style.display = 'none'
})
9. Promise.any()
在 ES2021 中,引入了 Promise.any()
方法,这个方法也是接收一组 Promise
实例作为参数,包装为一个新的 Promise
实例返回。
只要实例中有一个状态改变为 resolved
,包装函数就会变为 resolved
状态,如果所有的实例参数都为 rejected
状态,那么包装函数也会变为 rejected
状态。
虽然 Promise.any()
和 Promise.race()
很像,只有一点不同:就是 Promise.any()
不会因为某个 Promise
变为 rejected
状态而结束,必须等到所有参数 promise
都变为 rejected
状态才会结束。
简单来说:
Promise.any()
: 返回的是第一个状态改变为 resolved
,获取先成功的异步操作Promise.race()
: 返回的是第一个状态改变的 Promise
,获取先完成的异步操作
let p1 = Promise.reject('reject A')
let p2 = Promise.resolve('resolve A')
let p3 = Promise.resolve('resolve B')
let p4 = Promise.reject('reject B')
Promise.any([p1, p2, p3, p4]).then((value) => {
console.log(value) // resolve A
})
Promise.race([p1, p2, p3, p4])
.then((value) => {
console.log(value)
})
.catch((error) => {
console.log(error) // reject A
})
10 总结
Pomise
具有三种状态,pending
、resolved
、rejected
Promise
可以通过then
指定成功和失败的回调函数,并且默认返回一个Promise.resolve()
Promise.resolve()
和Promise.reject()
其实就是Promise
的简写形式Promise.catch()
可以作为前面promise
的rejected
状态的回调函数Promise.all()
可以获取一组Promise
成功的返回值,或者第一个失败的返回值Promise.allSettled()
可以获取一组Promise
的返回数据Promise.race()
可以获取一组Promise
中最先完成的异步操作Promise.finally()
状态改变后就会执行 ( 无论是resolve
还是reject
)Promise.any()
可以获取一组Promise
中最先成功的异步操作
#cnblogs_post_body li { margin-top: 20px }