手写promise经常出现在各大面试题上,本次我尝试着用自己的思路写了一下promise,发现还挺不好写的,前前后后用了4个方案,都没有比较好的通过测试,最后皇天不付有心人,下面是我给出的 promise 思路。
本代码实现了promsie基础的then、catch功能,以及链式调用、值保存、Promise.all、Promise.race、Promise.resolve、Promise.reject 功能,并且通过几个案例进行了测试。
有几个需要注意的地方:
- 引入了队列缓存then后面的回调,主要应对同一个promise多次调用then的情况
- 在处理 handleQueue的时候,并没有马上执行,而是使用了 setImmediate() ,在主程序执行完毕以后再执行,主要是为了避免使用 Promsie.resolve()的时候,调用resolve时,队列还没有形成
- 每一次then或者catch都返回一个新的promise,而这个promise与回调函数维持一个关系,当在执行handleQueue的时候会通过回调找到对应promise,然后执行catch操作或者将promise对应的queue赋值给一个新的promsie,从而执行队列里面的函数。
const {
setImmediate
} = require('timers')
let Promise1 = function (cb) {
this.state = 'unfulfilled';
this.isPromise = true
this.queue = [] // 通过一个队列缓存then里面的函数,主要应对同一个premise多次调用then的情况
// 定义resolve函数
let resolve = (data) => {
this.state = 'fulfilled'
// 先将成功数据进行缓存
this.successData = data
// 为了防止马上执行的时候队列还没有赋值给新的promise
this.handleQueue('success', data)
}
// 定义reject函数
let reject = (err) => {
this.state = 'failed'
// 先将失败数据进行缓存
this.errData = err
this.handleQueue('error', err)
}
cb(resolve, reject)
}
Promise1.prototype.handleQueue = function (type, data) {
setImmediate(() => {
let current
// 如果没有queue,那么就直接返回
if (this.queue.length === 0) {
return
}
current = this.queue.shift()
while (current) {
let cb
if (this.state === 'fulfilled') {
cb = current && typeof current === 'object' && current.success
} else {
cb = current && typeof current === 'object' && current.error
// 如果没有找到,那么就查找与成功回调关联的promise上的queue
if (!cb) {
cb = current.success && current.success.promise && current.success.promise.queue && current.success.promise.queue[0].error
// 如果找到了,那么就说明这个promise包含catch,将关联promise的queue去掉第一个
if (cb) {
current.success.promise.queue.shift()
}
}
}
// // 如果有成功回调就进行调用
if (cb) {
let promise = cb(data)
// 如果当前返回的是promise,那么将剩下的队列赋值给新的 promise.queue,然后立即结束循环
if (promise && promise.isPromise) {
// 将绑定在回调函数上面的 promise.queue 赋值给当前 promise 的 queue
promise.queue = (current.promise && current.promise.queue) || []
// return
} else { // 其余情况,重新生成一个新的promise,这种情况就只有resolve的情况
let p = Promise1.resolve(promise)
p.queue = (current.promise && current.promise.queue) || []
}
} else { // 否则说明没有设置对应的回调,那么就应该结束后面的执行
return
}
current = this.queue.shift()
}
})
}
Promise1.prototype.then = function (fulfilledHandler, errorHandler) {
if (typeof fulfilledHandler !== 'function') {
throw new Error('then 的第一个参数必须为函数')
}
if (errorHandler && (typeof errorHandler !== 'function')) {
throw new Error('then 的第二个函数必须为一个函数')
}
// 如果已经有数据了,那么直接调用
if (this.successData) {
fulfilledHandler(this.successData)
return
}
if (this.errData && errorHandler) {
errorHandler(this.errData)
return
}
// 将成功函数和失败函数封装成一个对象缓存进
this.queue.push({
success: fulfilledHandler,
error: errorHandler
})
const pTemp = Promise1.resolve()
// 将返回的 promise 与回调函数进行关联
fulfilledHandler.promise = pTemp
if (errorHandler) {
errorHandler.promise = pTemp
}
// 返回一个新的 promise
return pTemp;
};
Promise1.prototype.catch = function (errorHandler) {
if (typeof errorHandler !== 'function') {
throw new Error('catch 的第一个参数必须为函数')
}
// 如果有数据了就直接返回
if (this.errData) {
errorHandler(this.errData)
return
}
this.queue.push({
error: errorHandler
})
const pTemp = Promise1.reject()
// 将返回的 promise 与回调函数进行关联
errorHandler.promise = pTemp
return pTemp;
};
Promise1.resolve = function (val) {
return new Promise1((resolve) => {
resolve(val)
})
}
Promise1.reject = function (val) {
return new Promise1((resolve, reject) => {
reject(val)
})
}
Promise1.all = function (promises) {
let arr = []
let length = promises.length
return new Promise1((resolve, reject) => {
promises.every((promise, i) => {
// 这儿兼容 promise 的情况,如果是非 promise。那么就直接返回
if (promise && typeof promise === 'object' && promise.isPromise) {
promise.then(data => {
// console.log('data', data)
arr[i] = data
// 这个判断必须在then里面
length--
if (length === 0) {
resolve(arr)
}
}, err => {
reject(err)
})
// console.log('这儿是all里面的promise.queue', promise.queue)
} else {
arr[i] = promise
length--
if (length === 0) {
resolve(arr)
return false
}
}
return true
});
})
}
Promise1.race = function (promises) {
return new Promise((resolve, reject) => {
promises.every((promise, i) => {
// 返回第一个完成的
if (promise && typeof promise === 'object' && promise.isPromise) {
promise.then(data => {
resolve(data)
}, err => {
reject(err)
})
} else {
resolve(promise)
return false
}
// console.log('这儿是race里面的promise.queue', promise.queue)
return true
});
})
}
var p1, p2, p3
// 测试用例1 立即resovle的数据保存
// p1 = Promise1.resolve(123)
// p1.then(res => {
// console.log('res', res)
// })
// setTimeout(() => {
// p1.then(res => {
// console.log('res1', res)
// })
// }, 1000)
// 测试用例2 延迟resovle的数据保存
// p1 = new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve(111)
// }, 1000)
// })
// p1.then(res => {
// console.log('res', res)
// })
// setTimeout(() => {
// p1.then(res => {
// console.log('res1', res)
// })
// }, 2000)
// 测试用例3 立即reject的数据进行保存
// p1 = Promise1.reject(123)
// p1.catch(res => {
// console.log('err', res)
// })
// setTimeout(() => {
// p1.catch(res => {
// console.log('err1', res)
// })
// }, 1000)
// 测试用例4 延迟reject的数据保存
// p1 = new Promise((resolve, reject) => {
// setTimeout(() => {
// reject(111)
// }, 1000)
// })
// p1.catch(err => {
// console.log('err', err)
// })
// setTimeout(() => {
// p1.catch(err => {
// console.log('err1', err)
// })
// }, 2000)
// 测试用例5 链式使用
// Promise.resolve(111).then(res => {
// console.log('res', res)
// return 222
// }, err => {
// console.log('err', err)
// }).then(res => {
// console.log('res1', res)
// return Promise.reject(3333)
// }).then(res => {
// console.log('res', res)
// }).catch(err => {
// console.log('err2', err)
// })
// 测试用例6 promise.all / promise.race 同时使用
// p1 = new Promise1((resolve, reject) => {
// setTimeout(() => {
// resolve(1111)
// }, 1000)
// })
// p2 = new Promise1((resolve, reject) => {
// setTimeout(() => {
// resolve(2222)
// }, 2000)
// })
// p3 = new Promise1((resolve, reject) => {
// setTimeout(() => {
// resolve(3333)
// }, 3000)
// })
// Promise1.all([p1, p2, p3]).then(([res1, res2, res3]) => {
// console.log('res1', res1)
// console.log('res2', res2)
// console.log('res3', res3)
// }).catch(err => {
// console.log('err', err)
// })
// Promise1.race([p1, p2, p3]).then((val) => {
// console.log('val', val)
// }).catch(err => {
// console.log('err', err)
// })