轻松实现链式调用Then, Catch的简易Promise
再过不久,我也要投身到求职的浪潮之中,一直听说今年行情太差,自己所在的公司也就我一个前端, 一年经验还没人带,实在太难受。为了不被拍死在沙滩上,只好再刷刷面试题。 看到一些手写代码的文章中有实现Promise的题目,自己也试着写了写,在这儿做个笔记,加深加深印象。 如有错误还请指正。
实现特性
实现Promise之前并没有系统看过A+规范,特性都是自己在控制台用原生Promise实验的,不对的地方还请指正。
- 状态转换(pending| fulfilled(resolved) | rejected)
- then 方法返回当前Promise
- 支持then,catch 方法链式调用,手动再次调用
- then 中可返回Promise对象
- then 带有成功或者失败回调参数then(success, failed)
- 保证then和catch顺序
- 捕获运行时错误
思路
看了几篇面试文,里面实现的Promise功能都不齐全,而且稍显复杂,所幸拜读过晨曦时梦见兮大佬的最简实现Promise, 里面很巧妙的实现了then的链式调用。20行代码就完成了一个带有then函数的Promise。感兴趣的同学可以多去看看。
回顾Promise用法,我们在Promise构造函数中传入一个可能带有异步操作的函数。
对创建的Promise对象链式调用then方法,每一次的then的第一个回调函数的参数是上一次的返回值。
- 我们将Promise看作是一次装弹射击的动作。
- 每次
.then(callback)就相当于是填充一颗子弹,用一个弹夹(数组)存储起来。 - 当弹药填充完毕时(也就是then收集完成时),依次射击(执行then回调)
- 当有下一轮射击时(再次手动添加then),重复2步骤。
如何得知then收集完毕呢?晨曦时梦见兮大佬将执行then回调的函数设计为setTimeout, 链式调用then就是普通代码。
new Promise(setTimeout)
.then(cb1)
.then(cb2)
其中setTimeout作为宏任务,会在then函数(收集回调,并没有执行回调)同步调用之后再执行,这就解决的监听then收集完成的问题。
实现
初始状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise (executor) {
const self = this
self.__CallBacks__ = [] // then 方法回调队列
self.__PromiseValue__ = undefined
self.__PromiseStatus__ = PENDING // 初始化状态
self.__resolve__ = function () { // 成功回调
// do something here .
}
executor(self.__resolve__, self.__reject__)
}
实现then函数
__CallBacks__就是弹夹,then传入的callback就是子弹,then当然就充当填充子弹的动作。
MyPromise.prototype.then = function ( callback ) {
this.__CallBacks__.push( callback )
return this // 返回当前promise对象
}
执行回调
__resolve__作为执行回调的方法,我们将它用setTimeout包裹
self.__resolve__ = function ( lastValue ) { // 类似于开始射击的动作
// 因为我们要先填充弹药(记录所有then回调)再射击
// 所以射击需要在最后执行 这里考虑使用setTimeout
setTimeout( () => {
if ( self.__PromiseStatus__ === PENDING ) self.__PromiseStatus__ = RESOLVED
self.__PromiseValue__ = lastValue
const nextCallBack = self.__CallBacks__.shift() // 每次射击(执行)时取出一颗子弹(回调)
if ( nextCallBack ) {
const param = nextCallBack( lastValue ) // 得到上一次执行的结果
///////////////////////////////////////////////////////////////////////////////
// 若回调返回的是Promise(内部Promise),我们应该等待其执行结束再执行外部逻辑 //
///////////////////////////////////////////////////////////////////////////////
param instanceof MyPromise
? param.then( self.__resolve__ )
: self.__resolve__( param )
}
} )
}
__resolve__中首次调用改变__PromiseStatus__状态。一旦修改后续不会改变。
然后shift出一个回调,执行该回调,拿到结果。
param.then能拿到内部Promise最后一次执行的结果反馈。
这里考虑若then中返回的是一个Promise对象,应该等待内部Promise完全执行完毕之后,
再执行自身的__resolve__,保证执行顺序正确。否则执行下一次__resolve__(也就是下一个回调)。
重新装弹
肯定会有同学在调试的时候发现,已经射击(「resolve」)一次后,后续继续添加子弹(then回调)都不会执行了。
var first = new MyPromise((resolve) => resolve(666))
.then(v => {
console.log('first then', v)
return 233
})
// 控制台打印出first then后,手动在控制台重新输入
first.then(v => console.log('second then', v))
「second then」没有被打印出来,这是因为上一次在构造函数中, 延迟执行了射击动作 「「resolve」 + setTimeout」。 如果再重新填充弹药,就不可能在执行一次初始化,也就不能射击了。 所以我们需要在下一轮弹药填充(then)时重新执行延迟射击。
MyPromise.prototype.then = function ( successCB ) {
// 当上一次射击结束时(上一次填充的弹药消耗完毕) 延迟执行下一次回调
if ( this.__PromiseStatus__ !== PENDING && this.__CallBacks__.length === 0) {
this.__resolve__(this.__PromiseValue__)
}
this.__CallBacks__.push( successCB )
return this // 返回当前promise对象
}
至此,一个没有异常捕获的简易Promise算是完成了。这个Promise可以链式调用,重新调用。 完整代码可在文末查看
实现catch
catch只会捕获它之前的reject回调或者异常抛出,一旦catch后,catch之前的then都会「失效」,也就是说catch和then是有顺序的。
所以我还是使用__CallBacks__来存储catch回调,为了区分,__CallBacks__存储的是一个对象{callback, type}
type 是区分catch 和 then 的标识。
MyPromise.prototype.then = function ( successCB, rejectCB ) {
if ( callback instanceof Function ) {
if ( this.__PromiseStatus__ !== PENDING && !this.__CallBacks__.some(t => t.type === 'then') ) this.__resolve__( this.__PromiseValue__ )
this.__CallBacks__.push( { callback: successCB, type: 'then' } )
}
if ( rejectCB instanceof Function ) {
this.__CallBacks__.push( { callback: rejectCB, type: 'catch' } )
}
return this
}
MyPromise.prototype.catch = function ( callback ) {
return this.then( undefined, callback )
}
可能有些同学不知道,then函数第二个参数就是catch回调,我们在then中实现,catch直接调用。
这里注意下,我们使用type区分回调后,想要再次执行回调时,不是判断__CallBacks__是否为空,而是判断__CallBacks__里面是否then为空
重写__resolve__以适配catch
__resolve__函数添加第二个参数isReject标识当前处理的目标是否为catch回调
self.__resolve__ = function ( lastValue, isReject = false ) {
setTimeout( () => {
if ( self.__PromiseStatus__ === PENDING ) self.__PromiseStatus__ = isReject ? REJECTED : RESOLVED
// 每次调用更新Promise值
self.__PromiseValue__ = lastValue
const cbType = isReject ? 'catch' : 'then'
let item
// 循环队列 直到找到第一个匹配的回调
while ( item = self.__CallBacks__.shift() ) {
if ( item && item.type === cbType) {
const param = item.callback( lastValue )
param instanceof MyPromise ? param.then( self.__resolve__, self.__reject__) : self.__resolve__( param )
break
}
}
} )
}
self.__reject__ = ( val ) => self.__resolve__( val, true )
executor( self.__resolve__, self.__reject__ )
上述代码也比较好理解,我们用一个while循环匹配当前目标回调类型cbType, 找到之后就执行。
如果这里没匹配到目标函数,之前shift推出的函数都会被丢弃,所以catch之前的then都不会生效。
如果then回调抛出了异常呢?
这也比较简单,我们将执行回调函数的地方用try catch包裹,一旦捕获到错误,调用__reject__执行用户逻辑
if ( item && item.type === cbType) {
try {
const param = item.callback( lastValue )
param instanceof MyPromise ? param.then( self.__resolve__, self.__reject__) : self.__resolve__( param )
} catch (e) {
self.__reject__( e )
}
break
}
出现了错误 但是没有catch函数怎么办?
当抛出错误后,程序会循环匹配catch回调,此时我们的代码会将__CallBacks__清空(因为全都是then回调,找不到catch),
此时再手动抛出错误就行
self.__resolve__ = function ( lastValue, isReject = false ) {
setTimeout( () => {
if ( self.__PromiseStatus__ === PENDING ) self.__PromiseStatus__ = isReject ? REJECTED : RESOLVED
self.__PromiseValue__ = lastValue
// 当失败/异常,并且没有回调(catch)时,手动抛出异常
if ( isReject && self.__CallBacks__.length === 0 ) throw new Error( lastValue )
const cbType = isReject ? 'catch' : 'then'
// ......
} )
}
代码示例
简易实现(then) 30行代码
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise (executor) {
const self = this
self.__CallBacks__ = []
self.__PromiseValue__ = undefined
self.__PromiseStatus__ = PENDING
self.__resolve__ = function ( lastValue ) {
setTimeout( () => {
if ( self.__PromiseStatus__ === PENDING ) self.__PromiseStatus__ = RESOLVED
self.__PromiseValue__ = lastValue
const nextCallBack = self.__CallBacks__.shift()
if ( nextCallBack ) {
const param = nextCallBack( lastValue )
param instanceof MyPromise ? param.then( self.__resolve__ ) : self.__resolve__( param )
}
} )
}
executor(self.__resolve__)
}
MyPromise.prototype.then = function ( successCB ) {
if ( this.__PromiseStatus__ !== PENDING && this.__CallBacks__.length === 0) {
this.__resolve__(this.__PromiseValue__)
}
this.__CallBacks__.push( successCB )
return this
}
进阶实现(then,catch) 50行代码
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise ( executor ) {
const self = this
self.__CallBacks__ = []
self.__PromiseValue__ = undefined
self.__PromiseStatus__ = PENDING
self.__resolve__ = function ( lastValue, isReject = false ) {
setTimeout( () => {
if ( self.__PromiseStatus__ === PENDING ) self.__PromiseStatus__ = isReject ? REJECTED : RESOLVED
self.__PromiseValue__ = lastValue
if ( isReject && self.__CallBacks__.length === 0 ) throw new Error( lastValue )
const cbType = isReject ? 'catch' : 'then'
let item
while ( item = self.__CallBacks__.shift() ) {
if ( item && item.type === cbType) {
try {
const param = item.callback( lastValue )
param instanceof MyPromise ? param.then( self.__resolve__, self.__reject__) : self.__resolve__( param )
} catch (e) {
self.__reject__( e )
}
break
}
}
} )
}
self.__reject__ = ( val ) => self.__resolve__( val, true )
executor( self.__resolve__, self.__reject__ )
}
MyPromise.prototype.then = function ( callback, rejectCallback ) {
if ( callback instanceof Function ) {
if ( this.__PromiseStatus__ !== PENDING && !this.__CallBacks__.some(t => t.type === 'then') )
this.__resolve__( this.__PromiseValue__ )
this.__CallBacks__.push( { callback, type: 'then' } )
}
if ( rejectCallback instanceof Function ) {
this.__CallBacks__.push( { callback: rejectCallback, type: 'catch' } )
}
return this
}
MyPromise.prototype.catch = function ( callback ) {
return this.then( undefined, callback )
}
本文使用 mdnice 排版