前端面试题之Promise问题
前言
点击查看代码片段1在我们日常开发中会遇到很多异步的情况,比如涉及到
网络请求(ajax,axios等)
,定时器
这些,对于这些异步操作我们如果需要拿到他们操作后的结果,就需要使用到回调函数。拿请求来说,如果我们需要拿到请求回来的数据我们就需要利用回调函数(见代码片段1),以下所有的请求都是使用jQuery的ajax模拟。
// 代码片段1
$.ajax({
url: 'url',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
},
success: function(res){
// success就是响应成功的回调函数,在此处可以获取到响应返回的内容
console.log(res)
}
})
点击查看代码片段2在网络请求中会遇到请求之前的依赖,一个请求会依赖于另一个请求的时候,就会需要出现回调嵌套的问题(代码片段2)
// 代码片段2
// 请求2的请求参数是请求1的响应结果
// 请求3的请求参数是请求2的响应结果
$.ajax({
url: '请求1的地址',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
},
success: function(res){
// res是请求1的响应结果
console.log(res)
$.ajax({
url: '请求2的地址',
type: 'post',
data: {
// 参数1是请求1的响应结果
参数1: res,
参数2: 值2
},
success: function(res2){
// res2是请求2的响应结果
$.ajax({
url: '请求3的地址',
type: 'post',
data: {
// 参数1是请求2的响应结果
参数1: res2,
参数2: 值2
},
success: function(res3){
// res3是最终请求的结果
console.log(res3)
}
})
}
})
}
})
点击查看代码片段3这样的代码就出现了js编程里面的著名
回调地狱问题
,为了解决这个问题我们需要利用es2015的promise和es2016的await来解决(代码片段3)
// 代码片段3
// await不能写在同步的代码里面,会阻塞整个程序的进程,只能写在异步的代码里面
// async可以修饰一个函数让这个函数变成一个异步的函数
async function getRes(){
// $.ajax() 返回的是一个promise对象
// await可以等待一个promise状态结束,拿到响应的结果
const res1 = await $.ajax({
url: '请求1的地址',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
}
})
const res2 = await $.ajax({
url: '请求2的地址',
type: 'post',
data: {
参数1: res,
参数2: 值2
}
})
const res3 = await $.ajax({
url: '请求3的地址',
type: 'post',
data: {
参数1: res2,
参数2: 值2
}
})
}
// 调用异步函数
getRes()
利用promise和await就可以将之前的回调嵌套地狱改成同步的代码方式,但是这里面大家要注意await的使用事项,await不允许出现在同步代码块里面,会阻塞同步代码执行,必须写在异步的代码块里面,加async让getRes函数成为一个异步的函数,这样getRes执行不会阻塞全局,getRes函数内部代码就是一个同步的方式执行,就解决了回调嵌套的问题。本来故事到这里就该说goodBye了,但是现在很多面试,尤其是一些大厂的面试要求我们自己实现promise和await,这章节先给大家实现promise。
Promise的封装
1. promise的介绍和使用
-
状态(state)
- pending
- promise的初始状态,并且此状态可以转换成
fulfilled
和rejected
- promise的初始状态,并且此状态可以转换成
- fulfilled
- promise的成功状态,不可以转换其他状态,并且必须有一个不可改变的最终值
value
- promise的成功状态,不可以转换其他状态,并且必须有一个不可改变的最终值
- rejected
- promise的失败状态,不可以转换其他状态,并且必须有一个不可改变的原因
reason
- promise的失败状态,不可以转换其他状态,并且必须有一个不可改变的原因
- pending
-
术语
-
解决(fulfill)
当调用resolve方法时promise的状态变成fulfill
-
拒绝(reject)
当调用reject方法时promise的状态变成reject
-
终值(value)
所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
-
拒因(reason)
也就是拒绝(失败)的原因,指在 promise 被拒绝时传递给拒绝回调的值。
-
-
面向对象编程方式
- Promise是面向对象编程方式,对应的构造函数是
Promise
- 使用Promise的时候需要创建一个实例对象,使用实例对象的方法
- Promise构造函数实例化的时候需要传递一个函数(handler),此函数里面可以接收两个参数,一个函数叫做resolve,一个函数叫做reject
- 调用resolve时候promise实例的状态变为 fulfilled
- 调用reject时候promise实例的状态变为 rejected
- 此处要注意一个promise实例只有一个状态,也就是不能同时调用resolve和reject
- Promise是面向对象编程方式,对应的构造函数是
// promise01就是实例化Promise构造函数得到的实例对象
const promise01 = new Promise((resolve,reject)=>{
// 此处调用resolve时promise状态会变成fulfilled
// 此处调用reject时promise状态会变成rejected
})
- 实例对象的方法
- then方法
- 该方法有两个参数,可选,分别对应onFulfilled,onRejected
- 当状态为fulfilled时,调用onFulfilled,得到一个终值 value
- 当状态为rejected时,调用onRejected,得到一个拒因 reason
- catch方法
- 该方法有一个参数,可选,对应onRejected
- 当状态为rejected时,调用onRejected,得到一个拒因
- finally方法
- 该方法有一个参数,可选
- 该方法无论是promise状态成功还是失败都会调用
- 注意:
- 实例方法可以实现链式调用,因为每一次方法调用完成之后都会返回一个promise实例
- then方法可以调用多次
- then方法
const promise01 = new Promise((resolve,reject)=>{
})
// 使用实例对象方法 链式调用
promise01.then(
// onFulfilled
function(value){
// 当promise实例的状态为fulfilled调用
},
// onRejected
function(reason){
// 当promise实例的状态为rejected调用
}
).then(
// then方法可以调用多次
// 并且调用可以不传递参数
).catch(function(reason){
// 当promise实例的状态为rejected调用
}).finally(function(){
// 无论promise实例的状态为fulfilled或者rejected都会调用
})
2. 实现手写Promise
-
实现Promise的构造函数
- 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
- handler函数需要两个形式参数 对应的是类自身的两个方法
- resolve方法
- 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
- 调用方法promise的状态变为fulfilled
- 调用该方法会得到一个终值value
- 该方法可能异步调用
- 该方法里面会用到实例的this,在调用的时候需要改变this的指向
- reject方法
- 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
- 调用方法promise的状态变为rejected
- 调用该方法会得到一个拒因reason
- 该方法可能异步调用
- 该方法里面会用到实例的this,在调用的时候需要改变this的指向
- resolve方法
- 实例里面属性
- value 终值
- reason 拒因
- state 状态
- 状态可以作为静态的属性,也可以作为实例的属性
- 状态会多次用于判断,所以建议用常量保存
- 实例里面的方法
- then
- catch
- finally
- 类自身的方法
- resolve
- reject
// promise.js // 状态会多次用于判断,所以建议用常量保存 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class Promise{ constructor(handler){ // 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常 if(typeof handler !== 'function'){ throw new TypeError(`Promise构造函数的参数${handler}不是一个函数`) } this.state = PENDING // 用来存储promise的状态 初始化状态为pending this.reason = undefined // 拒因 this.value = undefined // 终值 // resolve,reject方法里面会用到实例的this,在调用的时候需要改变this的指向 handler(this.resolve.bind(this), this.reject.bind(this)) } resolve(value){ // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用 if(this.state !== PENDING) return // 调用方法promise的状态变为fulfilled this.state = FULFILLED // 调用该方法会得到一个终值value this.value = value } reject(reason){ // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用 if(this.state !== PENDING) return // 调用方法promise的状态变为rejected this.state = REJECTED // 调用该方法会得到一个拒因reason this.reason = reason } }
// 试错 const p = new Promise(1) console.log(p) // promise.js:9 Uncaught TypeError: Promise构造函数的参数1不是一个函数 // 正确操作 // 状态成功 const p = new Promise((resolve, reject) => { resolve() }) console.log(p) // Promise {state: 'fulfilled', reason: undefined, value: undefined} // 状态失败 const p = new Promise((resolve, reject) => { reject() }) console.log(p) // Promise {state: 'rejected', reason: undefined, value: undefined}
-
实现实例then方法
-
then方法接受两个参数,两个参数类型是函数
- onFulfilled
- 需要对onFulfilled类型进行验证,如果不是一个函数就设置为默认函数
function(value){return value}
,这个函数是获取终值 - 该函数在promise状态为fulfilled时候调用,传入终值
- 需要对onFulfilled类型进行验证,如果不是一个函数就设置为默认函数
- onRejected
- 需要对onRejected类型进行验证,如果不是一个函数就设置为一个默认函数
function(err){throw err}
,这个函数是获取拒因 - 该函数在promise状态为rejected时候调用,传入拒因
- 需要对onRejected类型进行验证,如果不是一个函数就设置为一个默认函数
// promise.js const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class Promise{ constructor(handler){...} resolve(value){...} reject(reason){...} then(onFulfilled, onRejected){ // 参数可选要进行判断 如果传递不是一个函数 默认一个函数 onFulfilled = typeof onFulfilled==='function' ? onFulfilled : value => value; onRejected = typeof onRejected==='function' ? onRejected : err => {throw err}; if(this.state === FULFILLED){ // 当promise状态为fulfilled时候调用onFulfilled onFulfilled(this.value) } if(this.state === REJECTED){ // 当promise状态为rejected时候调用onRejected onRejected(this.reason) } } }
// test.js // 成功状态 const p = new Promise((resolve, reject) => { resolve('success') }) p.then( res => { console.log('调用了onFullfilled') console.log(res) }, err => { console.log('调用了onRejected') console.log(err) } ) // 控制台输出 调用了onFullfilled sucess // 失败状态 const p = new Promise((resolve, reject) => { reject('error') }) p.then( res => { console.log(res) }, err => { console.log(err) } ) // 控制台输出 调用了onRejected error // handler函数异步调用resolve const p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve('success') },3000) }) p.then( res => { console.log('调用了onFullfilled') console.log(res) }, err => { console.log('调用了onRejected') console.log(err) } ) // 控制台没有任何内容输出 onFulfilled函数并没有被调用
- 此时经过测试,目前的promise可以正常的调用,此时针对于同步的调用没有问题,但是在
handler
里面resolve
和reject
大部分情况下都是异步调用的,比如说延迟3s调用,此时then里面的onFulfilled
和onRejected
是无法执行的,因为then函数在执行的时候promise的状态是pending,onFulfilled
和onRejected
函数里面是做了判断,没有对pendng状态进行处理,针对于pending状态的需要处理
// promise.js // ... class Promise { // 用来存储状态成功的回调函数 // 用来存储状态失败的回调函数 successCallBack = null errorCallBack = null constructor(handler) {...} resolve(value) { // ... // 异步调用的时候执行存储的onFulfilled函数,传入终值 this.successCallBack(this.value) } reject(reason) { // ... // 异步调用的时候执行存储的onRejected函数,传入拒因 this.errorCallBack(this.reason) } then(onFulfilled, onRejected) { // ... if (this.state === PENDING) { // 当resolve或者reject异步调用,then执行的时候promise状态等待 // 将onFulfilled和onReject函数存储起来 this.successCallBack = onFulfilled this.errorCallBack = onRejected } } }
// test.js // 异步调用resolve const p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve('success') },3000) }) p.then( res => { console.log('调用了onFullfilled') console.log(res) }, err => { console.log('调用了onRejected') console.log(err) } ) // 3s后控制台输出 调用了onFullfilled success
- 此时promise的handler就可以异步调用
resolve
或reject
,then里面可以获取到终值或据因 - then函数支持多次调用,所以此时then的回调函数就不止一个,我们将之前的变量改成数组用来存储then的回调函数
// promise.js // ... class Promise { // 用数组的方式存储失败和成功的回调函数 successCallBack = [] errorCallBack = [] constructor(handler) {...} resolve(value) { // ... // 遍历数组将所有成功的函数执行 this.successCallBack.forEach(fn=> fn()) } reject(reason) { // ... // 遍历数组将所有失败的函数执行 this.errorCallBack.forEach(fn=>fn()) } then(onFulfilled, onRejected) { // ... if (this.state === PENDING) { // 当resolve或者reject异步调用,then执行的时候promise状态等待 // 将onFulfilled和onReject函数存储起来 this.successCallBack.push(()=>{ onFulfilled(this.value) }) this.errorCallBack.push(()=>{ onRejected(this.reason) }) } } }
- 测试调用了两次then,状态成功之后两次then的成功回调都会被执行
// test.js // 异步调用resolve const p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve('success') },3000) }) p.then( res => { console.log('调用了onFullfilled') console.log(res) }, err => { console.log('调用了onRejected') console.log(err) } ) p.then( res => { console.log('调用了onFullfilled01') console.log(res) }, err => { console.log('调用了onRejected01') console.log(err) } ) // 3s后控制台输出 调用了onFullfilled success 调用了onFullfilled01 success
- 但是目前then不支持链式调用,需要进行链式调用的支持
- 支持链式调用的条件
- 只有then函数调用返回一个promise对象,一个新的promise对象
newPromise
- then函数有一个返回值 这个返回值
result
就是新promise的onFulfill或onRejected的值
- 只有then函数调用返回一个promise对象,一个新的promise对象
- 接下来我们需要判断这个返回值
result
的类型- 一个具体值
- 一个新的promise
- 另外我们需要一个函数去处理这种链式调用的问题
handlerChainPromise
,这个函数接收参数如下- result 老的promise的then返回值,作为新promise的onFulfill或onRejected
- resolve成功处理函数
- reject失败处理函数
class Promise { then(onFulfilled, onRejected) { // 每一次调用返回一个promise return new Promise((resolve,reject) => { // 参数可选要进行判断 如果传递不是一个函数 默认一个函数 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; if (this.state === PENDING) { // 当resolve或者reject异步调用,then执行的时候promise状态等待 // 将onFulfilled和onReject函数存储起来 this.successCallBack.push(() => { let result = onFulfilled(this.value) this.handlerChainPromise(result, resolve, reject) }) this.errorCallBack.push(() => { let result = onRejected(this.reason) this.handlerChainPromise(result, resolve, reject) }) } if (this.state === FULFILLED) { // 当promise状态为fulfilled时候调用onFulfilled let result = onFulfilled(this.value) this.handlerChainPromise(result, resolve, reject) } if (this.state === REJECTED) { // 当promise状态为rejected时候调用onRejected let result = onRejected(this.reason) this.handlerChainPromise(result, resolve, reject) } }) } handlerChainPromise(result,resolve,reject){ // 如果返回的是一个promise就调用它的then方法 // 如果返回的是一个具体值就直接返回值 if(result instanceof Promise){ result.then(resolve, reject) }else{ resolve(result) } } }
- 测试代码如下
// 返回一个具体值(可以使任何类型) const p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve('success') },3000) }) p.then( res => { return 11 }, err => { console.log(err) } ).then( res=>{ console.log(res) } ) // 返回一个promsie const p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve('success') },3000) }) p.then( res => { return new Promise(resolve=>{ setTimeout(()=>{ resolve(22) }, 1000) }) }, err => { console.log(err) } ).then( res=>{ console.log(res) } )
- 以上一个完整的promise就封装成功了
- 完整代码如下
// 状态会多次用于判断,所以建议用常量保存 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class Promise { // 用来存储状态成功的回调函数 // 用来存储状态失败的回调函数 successCallBack = [] errorCallBack = [] constructor(handler) { // 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常 if (typeof handler !== 'function') { throw new TypeError(`Promise构造函数的参数${handler}不是一个函数`) } this.state = PENDING // 用来存储promise的状态 初始化状态为pending this.reason = undefined // 拒因 this.value = undefined // 终值 // resolve,reject方法里面会用到实例的this,在调用的时候需要改变this的指向 handler(this.resolve.bind(this), this.reject.bind(this)) } resolve(value) { // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用 if (this.state !== PENDING) return // 调用方法promise的状态变为fulfilled this.state = FULFILLED // 调用该方法会得到一个终值value this.value = value // 异步调用的时候执行存储的onFulfilled函数,传入终值 this.successCallBack.forEach(fn => fn()) } reject(reason) { // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用 if (this.state !== PENDING) return // 调用方法promise的状态变为rejected this.state = REJECTED // 调用该方法会得到一个拒因reason this.reason = reason // 异步调用的时候执行存储的onRejected函数,传入拒因 this.errorCallBack.forEach(fn => fn()) } then(onFulfilled, onRejected) { // 每一次调用返回一个promise return new Promise((resolve,reject) => { // 参数可选要进行判断 如果传递不是一个函数 默认一个函数 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; if (this.state === PENDING) { // 当resolve或者reject异步调用,then执行的时候promise状态等待 // 将onFulfilled和onReject函数存储起来 this.successCallBack.push(() => { let result = onFulfilled(this.value) this.handlerChainPromise(result, resolve, reject) }) this.errorCallBack.push(() => { let result = onRejected(this.reason) this.handlerChainPromise(result, resolve, reject) }) } if (this.state === FULFILLED) { // 当promise状态为fulfilled时候调用onFulfilled let result = onFulfilled(this.value) this.handlerChainPromise(result, resolve, reject) } if (this.state === REJECTED) { // 当promise状态为rejected时候调用onRejected let result = onRejected(this.reason) this.handlerChainPromise(result, resolve, reject) } }) } handlerChainPromise(result,resolve,reject){ // 如果返回的是一个promise就调用它的then方法 // 如果返回的是一个具体值就直接返回值 if(result instanceof Promise){ result.then(resolve, reject) }else{ resolve(result) } } }
- onFulfilled