很多小伙伴面试的时候遇到面试题让你手写Promise,实在是一个头两个大,内心OS:这家公司真装逼,面试造火箭,上班还不是拧螺丝,云云......,然后甩手兼摇头,说我不会,然后跟面试say byebye
那么面试官是不是在装逼呢?
首先大家要明白这道题往往是出现在中高级工程师面试的时候才会出现,一般考察的是面试者的综合能力:
1)考察面试者对Promise的用法的掌握程度,能写出Promise的链式写法、API、三种状态
2)能根据Promise的用法实现Promise的链式调用
3)能实现then、catch、finally方法,实现微任务事件队列
4)了解all和race的使用,实现all、race方法
5)考察面试者是否具备按照以上思路一步步分析问题并解决问题的能力
第1点,面试者需要厘清这道题所需要实现的API清单:
// 1、Promise的三种状态 pending——进行中 fulfilled——已完成 rejected——已失败
// 2、Promise的链式3调用写法
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
}).then((res) => {
console.log('进入then:', res)
}).catch((err) => {
console.log('进入catch', err)
}).finally(() => {
console.log('进入finally', p)
})
// 3、Promise.all与Promise.race
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 3000)
})
Promise.all([p1, p2, p3]).then(res => {
console.log('3秒后打印:', res)
})
Promise.race([p1, p2, p3]).then(res => {
console.log('1秒后打印:', res)
})
第2点,面试者需要实现Promise的基本框架:
class MyPromise {
static STATUS_PENDING = 'pending'
static STATUS_FULFILLED = 'fulfilled'
static STATUS_REJECTED = 'rejected'
constructor(callback) {
return this
}
then(resolver) {
return this
}
catch(reject) {
return this
}
finally(fn) {
return this
}
static all(promiseArray) {
return this
}
static race(promiseArray) {
return this
}
}
/*
基本实现
new MyPromise((resolve, reject) => {})
.then(() => {})
.catch(err => {})
.finally(() => {})
*/
第3点,实现then、catch、finally方法,实现微任务事件队列。这也是考验面试者脑子是否转的过来的关键一步,在这一步里面我们是需要实现then和catch方法:
class MyPromise {
static STATUS_PENDING = 'pending'
static STATUS_FULFILLED = 'fulfilled'
static STATUS_REJECTED = 'rejected'
constructor(callback) {
this.PromiseState = MyPromise.STATUS_PENDING
this.resolver = function () { }
this.reject = function () { }
this.finallyFn = function () { }
// 模拟微任务事件队列
setTimeout(() => {
callback(this.resolver, this.reject)
}, 0)
return this
}
then(resolver) {
let ctx = this
// 封装resolver,待promise回调执行结束后调用resolver时,再去执行this.resolver
this.resolver = function(res) {
try {
resolver(res)
ctx.PromiseState = MyPromise.STATUS_FULFILLED
} catch (err) {
ctx.PromiseState = MyPromise.STATUS_REJECTED
ctx.reject(err)
} finally {
ctx.finallyFn()
}
}
return this
}
catch(reject) {
this.reject = reject
return this
}
finally(fn) {
this.finallyFn = fn
return this
}
static all(promiseArray) {
return this
}
static race(promiseArray) {
return this
}
}
/*
let p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
}).then((res) => {
console.log('进入then:', res)
}).catch((err) => {
console.log('进入catch:', err)
}).finally(() => {
console.log('进入finally:', p)
})
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
}).then((res) => {
console.log('进入then:', res)
}).catch((err) => {
console.log('进入catch:', err)
}).finally(() => {
console.log('进入finally:', p)
})
*/
这里看代码其实并不多,主要是面试者要如何转换一下思维使then函数的回调在Promise异步事件后执行。其实写到这里如果你能给出答案,在面试官这里你已经过关了,但是卡在这里的人数不胜数
当然这里没有实现then的重复调用的写法,如果面试官继续问你怎么优化,那么你就需要改造一下:
class MyPromise {
static STATUS_PENDING = 'pending'
static STATUS_FULFILLED = 'fulfilled'
static STATUS_REJECTED = 'rejected'
constructor(callback) {
let ctx = this
this.PromiseState = MyPromise.STATUS_PENDING
this.resolvers = []
this.reject = function () { }
this.finallyFn = function () { }
// 模拟微任务事件队列
setTimeout(function () {
let resolver = ctx.resolvers.shift()
// callback代表promise的回调事件,resolvers[0]代表then的第一个回调
callback(resolver, ctx.reject)
}, 0)
return this
}
then(resolver) {
let ctx = this
// 封装resolver,待promise回调执行结束后调用resolver时,再去执行this.resolver
this.resolvers.push(function (res) {
try {
let result = resolver(res)
while (ctx.resolvers.length) {
if (ctx.PromiseState === MyPromise.STATUS_PENDING) {
let resolverFn = ctx.resolvers.shift()// 执行完一个回调,释放一个回调
resolverFn(result)
if (!ctx.resolvers.length) {
ctx.PromiseState = MyPromise.STATUS_FULFILLED
}
}
}
} catch (err) {
if (ctx.PromiseState !== MyPromise.STATUS_REJECTED) {
ctx.reject(err)
ctx.PromiseState = MyPromise.STATUS_REJECTED
}
} finally {
if (ctx.PromiseState === MyPromise.STATUS_FULFILLED || ctx.PromiseState === MyPromise.STATUS_REJECTED) {
ctx.finallyFn()
}
}
})
return this
}
catch(reject) {
this.reject = reject
return this
}
finally(fn) {
this.finallyFn = fn
return this
}
static all(promiseArray) {
return this
}
static race(promiseArray) {
return this
}
}
第四点,实现all和race,这里一方面也是考察面试者对all和race的使用,一方面也很考验面试者的编程能力:
Promise.all的使用
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 3000)
})
Promise.all([p1, p2, p3]).then(res => {
console.log('3秒后打印:', res)
})
按照需求实现all,这里其实大家看看就好了,一般也不会有那么变态的面试官会往下问,最多就考考你伪代码:
class MyPromise {
static STATUS_PENDING = 'pending'
static STATUS_FULFILLED = 'fulfilled'
static STATUS_REJECTED = 'rejected'
constructor(callback) {
let ctx = this
this.PromiseState = MyPromise.STATUS_PENDING
this.resolvers = []
this.reject = function () { }
this.finallyFn = function () { }
// 模拟微任务事件队列
setTimeout(function () {
let resolver = ctx.resolvers.shift()
// callback代表promise的回调事件,resolvers[0]代表then的第一个回调
callback(resolver, ctx.reject)
}, 0)
return this
}
then(resolver) {
let ctx = this
// 封装resolver,待promise回调执行结束后调用resolver时,再去执行this.resolver
this.resolvers.push(function (res) {
try {
let result = resolver(res)
while (ctx.resolvers.length) {
if (ctx.PromiseState === MyPromise.STATUS_PENDING) {
let resolverFn = ctx.resolvers.shift()// 执行完一个回调,释放一个回调
resolverFn(result)
if (!ctx.resolvers.length) {
ctx.PromiseState = MyPromise.STATUS_FULFILLED
}
}
}
} catch (err) {
if (ctx.PromiseState !== MyPromise.STATUS_REJECTED) {
ctx.reject(err)
ctx.PromiseState = MyPromise.STATUS_REJECTED
}
} finally {
if (ctx.PromiseState === MyPromise.STATUS_FULFILLED || ctx.PromiseState === MyPromise.STATUS_REJECTED) {
ctx.finallyFn()
}
}
})
return this
}
catch(reject) {
this.reject = reject
return this
}
finally(fn) {
this.finallyFn = fn
return this
}
static all(promiseArray) {
const maxResolveNum = promiseArray.length
let resolveNum = 0
let resolveArray = new Array(maxResolveNum)
promiseArray.forEach(function (promise, i) {
//为每个promise添加then方法
promise.then(function (res) {
resolveArray[i] = res
resolveNum += 1
return res
}).catch(err => {
throw err
})
});
return new MyPromise(function (resolve, reject) {
promiseArray.forEach(function (promise) {
promise.then(function (res) {
if (resolveNum === maxResolveNum) {
resolve(resolveArray)
}
return res
})
});
})
}
static race(promiseArray) {
return this
}
}
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
}).then(res => {
console.log('res:', res)
return res
})
let p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
}).then(res => {
console.log('res:', res)
return res
})
let p3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 3000)
}).then(res => {
console.log('res:', res)
return res
})
MyPromise.all([p1, p2, p3]).then(res => {
console.log('3秒后打印:', res)
})
实现race方法:
class MyPromise {
static STATUS_PENDING = 'pending'
static STATUS_FULFILLED = 'fulfilled'
static STATUS_REJECTED = 'rejected'
constructor(callback) {
let ctx = this
this.PromiseState = MyPromise.STATUS_PENDING
this.resolvers = []
this.reject = function () { }
this.finallyFn = function () { }
// 模拟微任务事件队列
setTimeout(function () {
let resolver = ctx.resolvers.shift()
// callback代表promise的回调事件,resolvers[0]代表then的第一个回调
callback(resolver, ctx.reject)
}, 0)
return this
}
then(resolver) {
let ctx = this
// 封装resolver,待promise回调执行结束后调用resolver时,再去执行this.resolver
this.resolvers.push(function (res) {
try {
let result = resolver(res)
while (ctx.resolvers.length) {
if (ctx.PromiseState === MyPromise.STATUS_PENDING) {
let resolverFn = ctx.resolvers.shift()// 执行完一个回调,释放一个回调
resolverFn(result)
if (!ctx.resolvers.length) {
ctx.PromiseState = MyPromise.STATUS_FULFILLED
}
}
}
} catch (err) {
if (ctx.PromiseState !== MyPromise.STATUS_REJECTED) {
ctx.reject(err)
ctx.PromiseState = MyPromise.STATUS_REJECTED
}
} finally {
if (ctx.PromiseState === MyPromise.STATUS_FULFILLED || ctx.PromiseState === MyPromise.STATUS_REJECTED) {
ctx.finallyFn()
}
}
})
return this
}
catch(reject) {
this.reject = reject
return this
}
finally(fn) {
this.finallyFn = fn
return this
}
static all(promiseArray) {
const maxResolveNum = promiseArray.length
let resolveNum = 0
let resolveArray = new Array(maxResolveNum)
promiseArray.forEach(function (promise, i) {
//为每个promise添加then方法
promise.then(function (res) {
resolveArray[i] = res
resolveNum += 1
return res
}).catch(err => {
throw err
})
});
return new MyPromise(function (resolve, reject) {
promiseArray.forEach(function (promise) {
promise.then(function (res) {
if (resolveNum === maxResolveNum) {
resolve(resolveArray)
}
return res
})
});
})
}
static race(promiseArray) {
let resolveNum = 0
promiseArray.forEach(function (promise, i) {
//为每个promise添加then方法
promise.then(function (res) {
resolveNum += 1
return res
}).catch(err => {
resolveNum += 1
throw err
})
});
return new MyPromise(function (resolve, reject) {
promiseArray.forEach(function (promise) {
promise.then(function (res) {
if (resolveNum === 1) {
resolve(res)
}
return res
})
});
})
}
}
整个实现过程其实实现起来还是比较有难度的,尤其是Promise的一些特性及细节处理。当然一般面试过程中并不会问得太过深,主要考察的还是逻辑思考方面的能力及应变能力。真要符合Promise A+规范,那就得大家多多思考啦