前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
前言
面试官:请手写一个Promise?(开门见山)
我:既然说到Promise,那我肯定得先介绍一下JavaScript异步编程的发展史吧,这样就理解为啥Promise会出现以及Promise解决了什么问题了吧。
- 阶段一:回调函数
- 阶段二:事件发布/订阅模型
- ...
面试官:我不关心什么异步编程发展史(不耐烦),这年头谁都知道Promise是解决了回调地狱的问题,我关心的是你的编码能力,你直接show you code,直接写!!!
我:好吧!(其实手写代码才是我的强项,嘻嘻!)
手写promise
先说下promise的三种状态:
PENDING:等待态,promise的初始状态FULFILLED:成功态,promise调用resolve函数后即会从PENDING等待态变为FULFILLED成功态。REJECTED:失败态:promise调用reject函数后即会从PENDING等待态变为REJECTED失败态
注意:
- promise的状态一旦发生变更,便无法再更改。比如调用
resolve从PEDING变为FULFILLED,它的状态就永远是FULFILLED了,再调用reject也无法从FULFILLED变成REJECTED- 状态只能从
PENDING变为FULFILLED或REJECTED,不能从FULFILLED或REJECTED返回到PENDING,这个也很好理解,状态只能前进不能倒退。
先看用法:
const p = new Promise((resolve, reject) => {resolve(111);
})
p.then((value) => {console.log(value)
}, (error) => {console.log(error)
})
首先,Promise肯定是一个类,所以我们才可以new它,然后Promise实例化的时候给它传入一个回调我们叫它executor方法,Promise内部会立即调用这个executor方法,并且会传入resolve和reject两个函数作为调用参数,另外在Promise类的原型上应该提供一个then方法,它里面可以传入两个回调,分别为Promise成功的回调和Promise失败的回调。调用resolve后会走入成功的回调中,调用reject后会走入失败的回调中。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'class Promise {constructor(executor) {this.value = undefinedthis.reason = undefinedthis.status = PENDINGconst resolve = (value) => {if (this.status === PENDING) {this.value = valuethis.status = FULFILLEDthis.onResolvedCallbacks.forEach(fn => fn())}}const reject = (reason) => {if (this.status === PENDING) {this.reason = reasonthis.status = REJECTEDthis.onRejectedCallbacks.forEach(fn => fn())}}executor(resolve, reject);}then(onFulfilled, onRejected) {if (this.status === FULFILLED) {onFulfilled && onFulfilled(this.value)}if (this.status === REJECTED) {onRejected && onRejected(this.reason)}}
}module.exports = Promise;
面试官:如果是异步调用resovle或者reject呢?
我:简单,用两个数组充当队列把then里边的回调存起来不就好了。
class Promise {constructor(executor) {// ...// 定义两个数组this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];const resolve = (value) => {if (this.status === PENDING) {this.value = valuethis.status = FULFILLEDthis.onResolvedCallbacks.forEach(fn => fn())}}const reject = (reason) => {if (this.status === PENDING) {this.reason = reasonthis.status = REJECTEDthis.onRejectedCallbacks.forEach(fn => fn())}}// 默认执行executor函数,并传入resolve和reject函数executor(resolve, reject)}then(onFulfilled, onRejected) {if (this.status === FULFILLED) {onFulfilled && onFulfilled(this.value)}if (this.status === REJECTED) {onRejected && onRejected(this.reason)}if (this.status === PENDING) {this.onResolvedCallbacks.push(() => {onFulfilled(this.value)})this.onRejectedCallbacks.push(() => {onRejected(this.reason)})}}
}
这里定义了两个数组onResolvedCallbacks和onRejectedCallbacks分别存储 then 里面成功的回调和失败的回调,然后再调用resolve和reject时分别循环执行这两个数组里存储的回调函数。
面试官:可以,那promise的链式调用是怎么实现的呢?
比如:下面这段代码:
const p = new Promise((resolve, reject) => {setTimeout(() => {resolve(111)}, 1000)
})
p.then((value1) => {console.log('value1', value1)return 222
}, (error1) => {console.log('error1', error1)
}).then((value2) => {console.log('value2', value2)}, (error2) => {console.log('error2', error2)
})
它的打印结果为:

这个是如何实现的呢?
我:这个也简单,它内部调用then方法时,返回了一个新的promise,并让这个新的promise接管了它下一个then方法。
注意:这里不能返回
this,这样会导致多个then方法全部受同一个promise控制。
class Promise {// ...then(onFulfilled, onRejected) {const promise2 = new Promise((resolve, reject) => {if (this.status === FULFILLED) {// onFulfilled方法可能返回值或者promiseconst x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)}if (this.status === REJECTED) {// onRejected方法可能返回值或者promiseconst x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)}if (this.status === PENDING) {this.onResolvedCallbacks.push(() => {const x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)})this.onRejectedCallbacks.push(() => {const x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)})}})return promise2}
}
最核心的就是resolvePromise,来看下它做了什么:
function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {return reject(new TypeError('UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>'))}let called// 判断x的类型 x是对象或函数才有可能是一个promiseif (typeof x === 'object' && x !== null || typeof x === 'function') {try {const then = x.thenif (typeof then === 'function') {// 只能认为它是一个promisethen.call(x, (y) => {if (called) returncalled = trueresolvePromise(promise2, y, resolve, reject)}, (r) => {if (called) returncalled = truereject(r)})}else {resolve(x)}} catch (e) {if (called) returncalled = truereject(e)}} else {resolve(x)}
}
- 首先,先判断新返回的一个promise
promise2是不是等于x,抛出错误UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>,这一步是防止内部的循环引用。 - 声明一个变量
called,相当于加了一把锁,让promise只能调用一次成功或者失败回调,防止死循环。 - 解析x,如果它的类型是
object并且不为null,或者它是一个函数,并且它有then方法,我们认为这是一个promise - 递归解析,
then里面再次调用resolvePromise
手写最后
因为promise在EventLoop里面是个微任务,不过我们可以简单通过setTimout模拟。
然后我们再加上一些报错的捕获代码以及一些参数的兼容代码,以及实现catch方法。
class Promise {constructor(executor) {// ...// 这里增加try catchtry {executor(this.resolve, this.reject)} catch (e) {reject(e)}}then(onFulfilled, onRejected) {// 这里兼容下 onFulfilled 和 onRejected 的传参onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => vonRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}const promise2 = new Promise((resolve, reject) => {if (this.status === FULFILLED) {// 用 setTimeout 模拟异步setTimeout(() => {try {const x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}}, 0)}if (this.status === REJECTED) {// 用 setTimeout 模拟异步setTimeout(() => {try {const x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}}, 0)}if (this.status === PENDING) {this.onResolvedCallbacks.push(() => {// 用 setTimeout 模拟异步setTimeout(() => {try {const x = onFulfilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}}, 0)})this.onRejectedCallbacks.push(() => {// 用 setTimeout 模拟异步setTimeout(() => {try {const x = onRejected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}}, 0)})}})return promise2}// catch函数实际上里面就是调用了then方法catch (errCallback) {return this.then(null, errCallback)}
}
executor执行时增加try catch,防止执行用户传入的函数直接就报错了,这时我们应该直接rejectpromise。- 调用
onFulfilled和onRejected时,需要包裹setTimeout。 ok,这样就大功告成了。最后我们来测试下我们写的promise是否符合规范。 catch函数实际上里面就是调用了then方法,然后第一个参数传null。
测试promise
promise是有规范的,即Promises/A+,我们可以跑一段脚本测试写的promise是否符合规范。
首先,需要在我们的promise增加如下代码:
// 测试脚本
Promise.defer = Promise.deferred = function () {let dfd = {}dfd.promise = new Promise((resolve, reject) => {dfd.resolve = resolvedfd.reject = reject})return dfd
}
然后安装promises-aplus-tests包,比如用npm可以使用命令npm install -g promises-aplus-tests安装到全局,然后使用命令promises-aplus-tests 文件名即可进行测试,里面有872测试用例,全部通过即可以认为这是一个标准的promise。

完美,最后面试官向你伸出了大拇指!
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库