一. 异步代码的困境
在
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 })
补充:
中断函数继续执行:
returnthrow 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,只是会调用rejectjs// 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所返回的结果
- 返回第一个为
