JavaScript面试必备技巧之手写一个Promise
作者:mick
很多同学在面试的时候都会被要求手写一个Promise,那么今天我总结了一些手写Promise的方法,可以跟着我的思路一起来实现一个Promise,让我们的面试更有把握。同时我们也会实现一下Promsie常见的方法比如:all、race、allSettled、any。
基本实现
首先我们可以用类来实现Promise,而且Promise有三种状态:pending、fulfilled、rejected。初始状态为pending。还需要对Promise的终值进行初始化。Promise还有两个方法resolve和reject。
Promise有四个特点:
- 执行了resolve,Promise状态就会变成fulfilled
- 执行了reject,Promise状态就会变成rejected
- Promise状态不可逆,第一次成功就永久为fulfilled,第一次失败就永久为rejected
- Promise中有throw的话,就相当于执行了rejected
下面我就来简单的实现一下吧
实现resolve和reject
class MyPromise { constructor(executor) { // 初始化值 this.initValue() // 初始化this指向 this.initBind() try { executor(this.resolve, this.reject) } catch (error) { this.reject(error) } } initValue() { // 初始化值 this.promiseResult = null this.promiseState = "pending" // 初始状态 } initBind() { this.resolve = this.resolve.bind(this) this.reject = this.reject.bind(this) } resolve(val) { this.promiseState = "fulfilled" this.promiseResult = val } reject(reason) { this.promiseState = "rejected" this.promiseResult = reason } }
测试一下吧
const test1 = new MyPromise((resolve, reject) => { resolve("success") }) console.log(test1) // MyPromise{ promiseResult: 'success', promiseState: 'fulfilled' } const test2 = new MyPromise((resolve, reject) => { reject("fail") }) console.log(test2) // MyPromise{ promiseResult: 'fail', promiseState: 'rejected' } const test3 = new MyPromise((resolve, reject) => { throw "fail" }) console.log(test3) // MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }
这里重点说一下initBind中为什么要给resolve和reject绑定this。我们可以看到在resolve和reject中使用了this.promiseState
和this.promiseResult
。我们需要把它的this绑定到实例才对。
状态不可变
如果我们执行下面的代码
const test = new MyPromise((resolve, reject) => { resolve("success") reject("fail") }) console.log(test) // MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }
这就不符合我们的预期了,因为我们需要的是状态不可变。所以我们将代码改造一下,这里只需要修改resolve和reject就可以了
resolve(val) { if (this.promiseState !== "pending") return this.promiseState = "fulfilled" this.promiseResult = val } reject(reason) { if (this.promiseState !== "pending") return this.promiseState = "rejected" this.promiseResult = reason }
当执行resolve或reject的时候,发现状态不是pending就说明状态已经改变了,直接return即可。
then
我们首先看下原始的Promise的then实现的效果
// 直接输出success const p1 = new Promise((resolve, reject) => { resolve("success") }).then( (res) => console.log(res), (err) => console.log(err) ) // 1s后输出fail const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("fail") }, 1000) }).then( (res) => console.log(res), (err) => console.log(err) ) // 链式调用 直接输出2000 const p3 = new Promise((resolve, reject) => { resolve(1000) }) .then( (res) => 2 * res, (err) => console.log(err) ) .then( (res) => console.log(res), (err) => console.log(err) )
由此可以得出结论:
- then 接受两个回调,一个是成功回调, 一个是失败的回调
- 当Promise为fulfilled执行成功的回调,为rejected 执行失败的回调
- 如果resolve或者reject在定时器里面执行,则定时器结束后再执行then
- then 支持链式调用,下一次then执行受上一次then返回值的影响
then实现
由结论1和2可以实现
then(onFulfilled, onRejected) { // 首先要校验两个回调是否是函数 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } if (this.promiseState === "fulfilled") { onFulfilled(this.promiseResult) } else if (this.promiseState === "rejected") { onRejected(this.promiseResult) } }
但是我们如何保证回调是在定时器结束后执行呢?首先在定时器结束之前Promise的状态一直是pending的,回调结束之后我们才能知道是fulfilled或者是rejected,所以在执行then的时候,如果promiseState是pending我们就把回调收集起来,当回调结束之后再触发。那使用什么来保存这些回调的呢?这里建议使用数组,因为一个Promise实例可能会多次执行then,用数组一个个保存就可以了
initValue() { // 初始化值 this.promiseResult = null this.promiseState = "pending" // 初始状态 this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] } resolve(val) { if (this.promiseState !== "pending") return this.promiseState = "fulfilled" this.promiseResult = val while (this.onFulfilledCallbacks.length) { this.onFulfilledCallbacks.shift()(this.promiseResult) } } reject(reason) { if (this.promiseState !== "pending") return this.promiseState = "rejected" this.promiseResult = reason while (this.onRejectedCallbacks.length) { this.onRejectedCallbacks.shift()(this.promiseResult) } } then(onFulfilled, onRejected) { // 首先要校验两个回调是否是函数 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } if (this.promiseState === "fulfilled") { onFulfilled(this.promiseResult) } else if (this.promiseState === "rejected") { onRejected(this.promiseResult) } else if (this.promiseState === "pending") { this.onFulfilledCallbacks.push(onFulfilled.bind(this)) this.onRejectedCallbacks.push(onRejected.bind(this)) } }
链式调用
我们再来重新看下链式调用的例子
// 链式调用 直接输出2000 const p3 = new Promise((resolve, reject) => { resolve(1000) }) .then( (res) => 2 * res, (err) => console.log(err) ) .then( (res) => console.log(res), (err) => console.log(err) ) // 链式调用 输出3000 const p4 = new Promise((resolve, reject) => { resolve(1000) }) .then( (res) => new Promise((resolve, reject) => resolve(3 * res)), (err) => console.log(err) ) .then( (res) => console.log(res), (err) => console.log(err) )
由此可得:
- then方法本身会返回一个新的Promise对象
- 如果返回值是promise对象,返回值为成功,新promise就是成功
- 如果返回值是promise对象,返回值为失败,新promise就是失败
- 如果返回值是非promise对象,新promise对象就是成功,值为此返回值
这里我们对then改造了一下
then(onFulfilled, onRejected) { // 首先要校验两个回调是否是函数 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } var thenPromise = new MyPromise((resolve, reject) => { const resolvePromise = (cb) => { try { const x = cb(this.promiseResult) if (x === thenPromise && x) { throw new Error("不能返回自身") } if (x instanceof MyPromise) { // 如果是promise 返回值为成功 新promise 就是成功 // 如果是promise 返回值失败 新promise 就是失败 x.then(resolve, reject) } else { // 如果不是promise 直接返回成功 resolve(x) } } catch (error) { reject(error) throw new Error(error) } } if (this.promiseState === "fulfilled") { resolvePromise(onFulfilled) } else if (this.promiseState === "rejected") { resolvePromise(onRejected) } else if (this.promiseState === "pending") { this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled)) this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected)) } }) return thenPromise }
then会返回一个新的Promise,在这个新的promise中定义了方法resolvePromise ,接收一个cb回调函数,这个cb就是传入的onFulfilled或者onRejected。如果这两个回调返回的不是promise,那结果直接resolve出去。如果是promise,那么就需要根据它的状态来决定下一步操作。那它到底是成功还是失败的呢,只有它的then知道。然后把resolve和reject当做回调传给then,如果x返回的是成功的Promise,则会执行resolve, 如果x返回的是失败的promise,则会执行reject。这样链式调用的then才会知道执行哪一个回调。
执行顺序
const p = new Promise((resolve, reject) => { resolve(1) }).then( (res) => console.log(res), (err) => console.log(err) ) console.log(2)
为了实现类似的功能,使用setTimeout代替
看下完整代码
class MyPromise { constructor(executor) { // 初始化值 this.initValue() // 初始化this指向 this.initBind() try { executor(this.resolve, this.reject) } catch (error) { this.reject(error) } } initValue() { // 初始化值 this.promiseResult = null this.promiseState = "pending" // 初始状态 this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] } initBind() { this.resolve = this.resolve.bind(this) this.reject = this.reject.bind(this) } resolve(val) { if (this.promiseState !== "pending") return this.promiseState = "fulfilled" this.promiseResult = val while (this.onFulfilledCallbacks.length) { this.onFulfilledCallbacks.shift()(this.promiseResult) } } reject(reason) { if (this.promiseState !== "pending") return this.promiseState = "rejected" this.promiseResult = reason while (this.onRejectedCallbacks.length) { this.onRejectedCallbacks.shift()(this.promiseResult) } } then(onFulfilled, onRejected) { // 首先要校验两个回调是否是函数 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } var thenPromise = new MyPromise((resolve, reject) => { const resolvePromise = (cb) => { setTimeout(() => { try { const x = cb(this.promiseResult) if (x === thenPromise && x) { throw new Error("不能返回自身") } if (x instanceof MyPromise) { // 如果是promise 返回值为成功 新promise 就是成功 // 如果是promise 返回值失败 新promise 就是失败 x.then(resolve, reject) } else { // 如果不是promise 直接返回成功 resolve(x) } } catch (error) { reject(error) throw new Error(error) } }) } if (this.promiseState === "fulfilled") { resolvePromise(onFulfilled) } else if (this.promiseState === "rejected") { resolvePromise(onRejected) } else if (this.promiseState === "pending") { this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled)) this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected)) } }) return thenPromise } }
其他方法
all
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 如果所有Promise都成功,则返回成功结果数组
- 如果有一个Promise失败,则返回这个失败的结果
static all(promiseList) { const result = [] const count = 0 return new MyPromise((resolve, reject) => { const addData = function (index, value) { result[index] = value count++ if (count === promiseList.length) resolve(result) } promiseList.forEach((promise, index) => { if (promise instanceof MyPromise) { promise.then( (res) => { addData(index, res) }, (err) => { reject(err) } ) } else { addData(index, promise) } }) }) }
race
- 接收一个Promise数组,数组中有非Promise项,则此项当做成功
- 哪个Promise最快得到结果,就返回那个结果,无论成功失败
race(promiseList) { return new MyPromise((resolve, reject) => { promiseList.forEach((promise) => { if (promise instanceof MyPromise) { promise.then( (res) => resolve(res), (err) => reject(err) ) } else { resolve(promise) } }) }) }
allSettled
- 接收一个Promise数组,数组中有非Promise项,则此项当做成功
- 把每个Promise的结果,集合成数组后返回
allSettled(promiseList) { const result = [] let count = 0 return new MyPromise((resolve, reject) => { const addData = function (status, value, i) { result[i] = { status, value } count++ if (count === promiseList.length) { resolve(result) } } promiseList.forEach((promise, index) => { if (promise instanceof MyPromise) { promise.then( (res) => { addData("fulfilled", res, index) }, (err) => { addData("reject", err, index) } ) } else { resolve(promise) } }) }) }
any
与all相反
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 如果有一个Promise成功,则返回这个成功结果
- 如果所有Promise都失败,则报错
static any(promiseList) { return new MyPromise((resolve, reject) => { let count = 0 promiseList.forEach((promise) => { if (promise instanceof MyPromise) { promise.then( (res) => { resolve(res) }, (err) => { count++ if (count === promiseList.length) { reject("error") } } ) } else { resolve(promise) } }) }) }
到此这篇关于JavaScript面试必备技巧之手写一个Promise的文章就介绍到这了,更多相关JavaScript手写Promise内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!