一. 异步代码的困境
在
ES6
出来之后,有很多关于Promise
的讲解、文章,也有很多经典的书籍讲解Promise
- 虽然等你学会
Promise
之后,会觉得Promise
不过如此 - 但是在初次接触的时候都会觉得这个东西不好理解
- 虽然等你学会
那么这里从一个实际的例子来作为切入点:
我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟)
如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去
如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息
jsfunction requestData(url, successCallback, failureCallback) { setTimeout(() => { if (url === 'http://later.org') { successCallback('发送成功了') } else { failureCallback('请求失败了') } }, 1000) } requestData('http://aaa/org', function(successMsg) { console.log(successMsg) }, function(failureMsg) { console.log(failureMsg) }) // 请求失败了 requestData('http://later.org', function(successMsg) { console.log(successMsg) }, function(failureMsg) { console.log(failureMsg) }) // 发送成功了
二. 认识 Promise 作用
在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
- 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等
- 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用
Promise
是通过链式调用的方式让回调函数变得可控,统一处理异步的方案我们来看一下
Promise
的API
是怎么样的:Promise
是一个类,可以翻译成:承诺、许诺、期约- 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个
Promise
的对象 - 在通过
new
创建Promise
对象时,我们需要传入一个回调函数,我们称之为executor
(执行者)- 这个回调函数会被立即执行,并且给传入另外两个回调函数
resolve
、reject
- 当我们调用
resolve
回调函数时,会执行Promise
对象的then
方法传入的回调函数 - 当我们调用
reject
回调函数时,会执行Promise
对象的catch
方法传入的回调函数
- 这个回调函数会被立即执行,并且给传入另外两个回调函数
那么有了
Promise
,我们就可以将之前的代码进行重构了:jsfunction request(url) { const promise = new Promise((resolve, reject) => { setTimeout(() => { if (url === 'http://later.org') { resolve('发送成功了') } else { reject('请求失败了') } }, 1000) }) return promise } request('http://aaa/org').then( res => { console.log(res) }).catch( err => { console.log(err) }) // 请求失败了 request('http://later.org').then( res => { console.log(res) }).catch( err => { console.log(err) }) // 发送成功了
三. Promise 基本使用
1. Executor 执行者
executor
:是在创建Promise
时需要传入的一个回调函数,该函数会被立即同步执行,因为该函数将在构造这个新Promise
对象过程中,被Promise
构造函数执行并且传入两个参数jsnew Promise(executor)
该
executor
是一段将输出与promise
联系起来的自定义代码。executor
的函数签名应为:jsnew promise((resolutionFunc, rejectionFunc) => {...})
resolutionFunc
与rejectionFunc
两个参数类型都是函数,可以使用任何名字。这两个函数的签名很简单:接受任何类型的单个参数- 该
executor
的返回值将被忽略 - 如果在该
executor
中抛出一个错误,该promise
将被拒绝
通常我们会在
Executor
中确定我们的Promise
状态:- 通过
resolve
,可以兑现(fulfilled
)Promise
的状态,我们也可以称之为已决议(resolved
) - 通过
reject
,可以拒绝(rejected
)Promise
的状态
注意:
- 一旦状态被确定下来,
Promise
的状态会被锁死,该Promise
的状态之后是不可更改的
在我们调用
resolve
的时候,如果resolve
传入的值本身不是一个Promise
,那么会将该Promise
的状态变成兑现(fulfilled
)在之后我们去调用
reject
时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise
状态)
- 通过
2. Promise 的状态变化
下面
Promise
使用过程,可以划分为三个状态:- 待定(
pending
): 初始状态,既没有被兑现,也没有被拒绝- 当执行
executor
中的代码时,处于该状态
- 当执行
- 已兑现(
fulfilled
): 意味着操作成功完成- 执行了
resolve
时,处于该状态,Promise
已经被兑现
- 执行了
- 已拒绝(
rejected
): 意味着操作失败- 执行了
reject
时,处于该状态,Promise
已经被拒绝
- 执行了
jsconst promise = new Promse((resolveFunc, rejectFunc) => { resolve('已兑现fulfilled') // 调用resolve,如果Promise状态值变为fulfilled,那么then传入的回调会被执行 reject('已拒绝rejected') // 调用reject,如果Promise状态值变为rejected,那么catch传入的回调函数会被执行 }) promise.then(res => { console.log(res) }).catch(err => { console.log(err) })
- 待定(
3. resolve 不同值的区别
情况一:如果
resolve
传入一个普通的值或者对象,那么这个值会作为then
回调的参数jsnew Promise((resolve, reject) => { // resolve('123') resolve({name: 'later'}) }).then(res => { console.log(res) // {name: 'later'} })
情况二:如果
resolve
中传入的是另外一个Promise
,那么这个新Promise
会决定原Promise
的状态jsnew Promise((resolve, reject) => { resolve(new Promise((resolve, reject) => { setTimeout(() => { resolve('第二个Promise的resolve') }, 1000) })) }).then(res => { console.log(res) // 第二个Promise的resolve }).catch(err => { console.log(err) })
情况三:如果
resolve
中传入的是实现了then
方法的一个对象,那么会执行该对象内部的then
方法,并且根据then
方法的结果来决定Promise
的状态jsnew Promise((resolve, reject) => { resolve({ name: 'later', then: function(resolve, reject) { resolve('thenable value') } }) }).then(res => { console.log(res) // thenable value })
四. Promise 实例方法
1. then 方法 - 接受两个参数
then
方法是Promise
对象上的一个实例方法:- 它其实是放在
Promise
的原型上的Promise.prototype.then
- 它其实是放在
then
方法接受两个参数:fulfilled
的回调函数:当状态变成fulfilled
时会回调的函数rejected
的回调函数:当状态变成rejected
时会回调的函数
jsconst p = new Promise((resolve, reject) => { resolve('已完成 fulfilled') }) p.then(res => { console.log(res) // 已完成 fulfilled }, err => { console.log(err) }) // 上面写法等价于下面这种写法 p.then(res => { console.log(res) // 已完成 fulfilled }).catch(err => { console.log(err) })
如果
promise
状态为rejected
,且then
调用传了第二个参数,同时也调用了catch
,则只会执行then
第二个参数中的代码jsconst p = new Promise((resolve, reject) => { reject('rejected') }) p.then(res => { console.log(res) }, err => { console.log('then中的err: ', err) // then中的err: rejected }).catch(err => { console.log('catch中的err: ', err) })
2. then 方法 - 多次调用
一个
Promise
的then
方法是可以被多次调用的:- 每次调用我们都可以传入对应的
fulfilled
回调 - 当
Promise
的状态变成fulfilled
的时候,这些then
方法中的回调函数都会被执行
jsconst p = new Promise((resolve, reject) => { resolve('已完成 fulfilled') }) p.then(res => {console.log(res)}) // 已完成 fulfilled p.then(res => {console.log(res)}) // 已完成 fulfilled p.then(res => {console.log(res)}) // 已完成 fulfilled
- 每次调用我们都可以传入对应的
3. then 方法 - 返回值
then
方法本身是有返回值的,它的返回值是一个Promise
,所以可以进行如下的链式调用:- 但是
then
方法返回的Promise
到底处于什么样的状态呢?
- 但是
then
方法返回的Promise
有三种状态:- 当
then
方法中的回调函数本身在执行的时候,那么它处于pending
状态 - 当
then
方法中的回调函数返回一个结果时,默认返回undefined
- 情况一:返回一个原始值或一个普通对象,那么返回的
Promise
处于fulfilled
状态,并且会将返回结果作为resolve
的参数 - 情况二:返回一个
Promise
,根据该promise
的状态决定返回的的Promise
的状态- 返回一个已经是接受状态的
Promise
,那么then
返回的Promise
也会成为接受状态,并且将那个Promise
的接受状态的回调函数的参数值作为该被返回的Promise
的接受状态回调函数的参数值 - 返回一个已经是拒绝状态的
Promise
,那么then
返回的Promise
也会成为拒绝状态,并且将那个Promise
的拒绝状态的回调函数的参数值作为该被返回的Promise
的拒绝状态回调函数的参数值 - 返回一个未定状态(
pending
的Promise
,那么then
返回Promise
的状态也是未定的,并且它的终态与那个Promise
的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise
变为终态时的回调函数的参数是相同的
- 返回一个已经是接受状态的
- 情况三:返回一个
thenable
值(内部有实现then
方法的对象),会调用then
方法来决定状态
- 情况一:返回一个原始值或一个普通对象,那么返回的
- 当
then
方法抛出一个异常时,那么它处于reject
状态
jsconst promise = new Promise((resolve, reject) => { resolve("111") }) // then方法本身是返回一个新的Promise, 这个新Promise的决议是等到then方法传入的回调函数有返回值时, 进行决议,Promise本身就是支持链式调用 promise.then(res => { console.log(res) // 111 // 1. 返回普通值 return "222" }).then(res => { console.log(res) // 222 // 2. 返回已完成状态的promise return new Promise((resolve, reject) => { resolve('333') }) }).then(res => { console.log(res) // 333 // 3. 返回一个带有then方法的对象(thenable值) return { then: function(resolve) { resolve('444') } } }).then(res => { console.log(res) // 444 // 4. 不显式指定返回值,默认返回undefined }).then(res => { console.log(res) // undefined // 5. 返回一个已拒绝状态的promise return new Promise((resolve, reject) => { reject('555') }) }).then(res => { console.log(res) }, err => { console.log(err) // 555 // 6. 抛出一个异常 throw new Error('抛出异常') }).then(res => { console.log(res) }, err => { console.log(err) // Error: 抛出异常 // 7. 返回一个未定状态的promise return new Promise((resolve, reject) => {}) }).then(res => { // 该回调函数不会被执行 console.log(res) })
- 当
4. catch 方法 - 多次调用
catch
方法也是Promise
对象上的一个实例方法:- 它也是放在
Promise
的原型上的Promise.prototype.catch
- 它也是放在
一个
Promise
的catch
方法是可以被多次调用的:- 每次调用我们都可以传入对应的
rejected
回调 - 当
Promise
的状态变成rejected
的时候,这些catch
方法中的回调函数都会被执行
jsconst p = new Promise((resolve, reject) => { reject('已拒绝 rejected') }) p.then(res => {}, err => {console.log(err)}) // 已拒绝 rejected p.then(res => {}, err => {console.log(err)}) // 已拒绝 rejected p.then(res => {}, err => {console.log(err)}) // 已拒绝 rejected
注意:
- 如果一个
promise
被拒绝(rejected)并且没有可调用的拒绝处理器(处理器可以是 Promise.prototype.then()、Promise.prototype.catch() 或 Promise.prototype.finally() ),则拒绝事件由宿主环境来提供。在浏览器中,这将导致未处理的拒绝(unhandledrejection
)事件。如果将一个处理器附加到一个已被拒绝,且已导致未处理的拒绝事件的promise
,将会触发rejectionhandled
事件 - 无法被
try...catch
捕获 - 在被触发的事件回调中,可以通过
event.preventDefault()
来阻止默认行为(这里就是在浏览器控制台报错)
- 每次调用我们都可以传入对应的
5. catch 方法 - 返回值
事实上
catch
方法也是会返回一个Promise
对象的,所以catch
方法后面我们可以继续调用then
方法或catch
方法:jsconst promise = new Promise((resolve, reject) => { reject("111") }) // catch方法也会返回一个新的Promise promise.catch(err => { console.log("第一个catch回调:", err) // catch回调: 111 return "222" }).then(res => { console.log("第一个then回调:", res) // then回调: 222 }).catch(err => { console.log("第二个catch回调:", err) }).then(res => { console.log("第二个then回调:", res) // 第二个then回调: undefined })
上面的代码,我们发现第二个
catch
方法中的回调函数并没有执行,这是为什么呢?- 因为第一个
then
方法中的回调执行完的时候,此时第一个then
方法返回的promise
状态已变成fulfilled
,而第二个catch
方法中传入的回调函数只有在rejected
状态下才会被执行
- 因为第一个
那为什么第二个
then
方法的回调函数会执行呢?- 因为第二个
catch
传入的回调并没有被执行,返回的promise
状态默认依然是fulfilled
- 因为第二个
那么如果我们希望继续执行第二个
catch
方法中的回调函数,需要在前面抛出一个异常:jsconst promise = new Promise((resolve, reject) => { reject("111") }) // catch方法也会返回一个新的romise promise.catch(err => { console.log("第一个catch回调:", err) // catch回调: 111 return "222" }).then(res => { console.log("第一个then回调:", res) // then回调: 222 throw new Error('11122') // 写在第一个catch方法回调中也行 }).catch(err => { console.log("第二个catch回调:", err) // 第二个catch回调: Error: 11122 }).then(res => { console.log("第二个then回调:", res) // 第二个then回调: undefined })
补充:
中断函数继续执行:
return
throw new Error()
yield
暂停(暂时性的中断)
上面几个方法针对普通函数,而非
promise
6. finally 方法
finally
是在ES9(ES2018)
中新增的一个特性:表示Promise
无论变成fulfilled
还是rejected
状态,最终都会执行传入的回调函数finally()
方法返回一个Promise
这避免了同样的语句需要在
then()
和catch()
中各写一次的情况finally
方法是不接收参数的,因为无论前面是fulfilled
状态,还是rejected
状态,它都会执行jsconst p = new Promise((resolve, reject) => { resolve('111') }) p.then(res => { console.log(res) // 111 }).catch(err => { console.log(err) }).finally(() => { console.log('执行finally方法传入的回调函数') // 执行finally方法传入的回调函数 })
五. Promise 类方法
1. resolve 方法
前面我们学习的
then
、catch
、finally
方法都属于Promise
的实例方法,都是存放在Promise
的prototype
上的- 下面我们再来学习一下
Promise
的类方法
- 下面我们再来学习一下
语法:
jsPromise.resolve(value)
- 返回一个带着给定值
value
解析过的Promise
对象
- 返回一个带着给定值
resolve
参数的形态:- 情况一:参数是一个普通的值或对象
- 情况二:参数本身是
Promise
- 情况三:参数是一个
thenable
有时候我们已经有一个现成的内容了,希望将其转成
Promise
来使用,这个时候我们可以使用Promise.resolve
方法来完成Promise.resolve
的用法相当于new Promise
,并且执行resolve
操作js// const p = new Promise((resolve) => { // resolve('111') // }) const p = Promise.resolve('111') // 等价于上面的写法,更简洁的写法 p.then(res => { console.log(res) // 111 })
2. reject 方法
reject
方法类似于resolve
方法,只是会将Promise
对象的状态设置为reject
状态Promise.reject
的用法相当于new Promise
,只是会调用reject
js// const p = new Promise((_, reject) => { // reject('111') // }) const p = Promise.reject('111') // 等价于上面的写法,更简洁的写法 p.catch(err => { console.log(err) // 111 })
Promise.reject
传入的参数无论是什么形态,都会直接作为reject
状态的参数传递到catch
的jsconst p = Promise.reject(new Promise((_, reject) => {})) p.catch(err => { console.log('catch: ', err) // catch: Promise {<pending>} })
3. Promise.all 方法
Promise.all()
方法接收一个promise
的iterable
类型(注:Array
、Map
、Set
都属于ES6
的iterable
类型)的输入,并且返回一个Promise
实例它的作用是将多个
Promise
包裹在一起形成一个新的Promise
新的
Promise
状态由包裹的所有Promise
共同决定:- 当所有的
Promise
状态变成fulfilled
状态时,新的Promise
状态为fulfilled
,并且会将所有Promise
的返回值组成一个数组 - 当有一个
Promise
状态为reject
时,新的Promise
状态为reject
,并且会将第一个reject
的返回值作为参数
jsconst p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p1 resolve") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p2 resolve") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 5000) }) const p4 = new Promise((resolve, reject) => { setTimeout(() => { reject('p4 reject') }, 1000) }) Promise.all([p1, p2, p3]).then(res => { console.log("all promise res:", res) // all promise res: ['p1 resolve', 'p2 resolve', 'p3 resolve'] }).catch(err => { console.log("all promise err:", err) }) Promise.all([p1, p2, p4]).then(res => { console.log("all promise res:", res) }).catch(err => { console.log("all promise err:", err) // all promise err: p4 reject })
- 当所有的
4. Promise.allSettled 方法
all
方法有一个缺陷:当有其中一个Promise
变成reject
状态时,新Promise
就会立即变成对应的reject
状态- 那么对于
resolved
的,以及依然处于pending
状态的Promise
,我们是获取不到对应的结果的
- 那么对于
在
ES11(ES2020)
中,添加了新的API
:Promise.allSettled
- 该方法会在所有的
Promise
都有结果(settled
),无论是fulfilled
,还是rejected
时,才会有最终的状态(终态) - 并且这个返回的
Promise
的结果一定是fulfilled
的,也就是说如果有调用then
方法,就一定会执行then
方法中传入的回调函数
jsconst p1 = new Promise((resolve, reject) => { setTimeout(() => { reject("p1 reject error") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p2 resolve") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 1000) }) Promise.allSettled([p1, p2, p3]).then(res => { console.log("all settled:", res) // all settled [{status: 'rejected', reason: 'p1 reject error'}, {status: 'fulfilled', value: 'p2 resolve'}, {status: 'fulfilled', value: 'p3 resolve'}] })
- 该方法会在所有的
我们来看一下打印的结果:
allSettled
的结果是一个数组,数组中存放着每一个Promise
的结果,并且是对应一个对象的- 这个对象中包含
status
状态,以及对应的value
值 - 如果
status
的值为fulfilled
,则结果对象上存在一个value
。如果值为rejected
,则存在一个reason
。value
(或reason
)反映了每个promise
决议(或拒绝)的值
5. Promise.race 方法
如果有一个
Promise
有了结果,我们就希望决定最终新Promise
的状态,那么可以使用race
方法:race
是竞技、竞赛的意思,表示多个Promise
相互竞争,谁先有结果,那么就使用谁的结果- 返回一个
promise
,一旦迭代器中的某个promise
已决议或已拒绝,返回的promise
就会已决议或已拒绝
jsconst p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p1 resolve") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("p2 reject error") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 1000) }) Promise.race([p1, p2, p3]).then(res => { console.log("race promise:", res) // race promise: p3 resolve }).catch(err => { console.log("race promise err:", err) })
6. Promise.any 方法
any
方法是ES12
中新增的方法,和race
方法是类似的:any
方法只要等到其中一个promise
变为fulfilled
状态,就会返回那个已完成状态的Promise
- 如果所有的
Promise
都是reject
的,就会返回一个失败的promise
,同时会报一个AggregateError
的错误
jsconst p1 = new Promise((resolve, reject) => { setTimeout(() => { reject("p1 reject error") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("p2 reject error") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { reject("p3 reject error") }, 1000) }) const p4 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p4 resolve") }, 500) }) Promise.any([p1, p2, p3]).then(res => { console.log("any promise res:", res) }).catch(err => { console.log("any promise err:", err) // any promise err: AggregateError: All promises were rejected }) Promise.any([p1, p2, p4]).then(res => { console.log("any promise res:", res) // any promise res: p4 resolve }).catch(err => { console.log("any promise err:", err) })
7. Promise 类方法总结
Promise.resolve()
:- 相当于
new Promise()
,并且执行resolve
操作
- 相当于
Promise.reject()
:- 相当于
new Promise
,只是会调用reject
- 相当于
Promise.all()
:- 当所有传入的
Promise
状态变成fulfilled
状态时,返回的新的Promise
状态为fulfilled
,并且将所有Promise
的返回值组成一个数组 - 当有一个
Promise
状态为reject
时,新的Promise
状态为reject
,并且会将第一个reject
的返回值作为参数
- 当所有传入的
Promise.allSettled()
:- 在所有的
Promise
都有结果(settled
),无论是fulfilled
,还是rejected
时,才会有最终的状态(终态),返回的Promise
的结果一定是fulfilled
的
- 在所有的
Promise.race()
:- 返回第一个状态为
fulfilled
或rejected
的promise
所返回的结果
- 返回第一个状态为
Promise.any()
:- 返回第一个为
fulfilled
状态的promise
所返回的结果
- 返回第一个为