手把手教你实现 Promise的使用方法
前言
很多JavaScript的初学者都曾感受过被回调地狱支配的恐惧,直至掌握了Promise语法才算解脱。虽然很多语言都早已内置了Promise,但是JavaScript中真正将其发扬光大的还是jQuery1.5对$.ajax的重构,支持了Promise,而且用法也和jQuery推崇的链式调用不谋而合。后来ES6出世,大家才开始进入全民Promise的时代,再后来ES8又引入了async语法,让JavaScript的异步写法更加优雅。
今天我们就一步一步来实现一个Promise,如果你还没有用过Promise,建议先熟悉一下Promise语法再来阅读本文。
构造函数
在已有的Promise/A+规范中并没有规定promise对象从何而来,在jQuery中通过调用$.Deferred()得到promise对象,ES6中通过实例化Promise类得到promise对象。这里我们使用ES的语法,构造一个类,通过实例化的方式返回promise对象,由于Promise已经存在,我们暂时给这个类取名为Deferred。
classDeferred{ constructor(callback){ constresolve=()=>{ //TODO } constreject=()=>{ //TODO } try{ callback(resolve,reject) }catch(error){ reject(error) } } }
构造函数接受一个callback,调用callback的时候需传入resolve、reject两个方法。
Promise的状态
Promise一共分为三个状态:
pending:等待中,这是Promise的初始状态;
fulfilled:已结束,正常调用resolve的状态;
rejected:已拒绝,内部出现错误,或者是调用reject之后的状态;
我们可以看到Promise在运行期间有一个状态,存储在[[PromiseState]]中。下面我们为Deferred添加一个状态。
//基础变量的定义 constSTATUS={ PENDING:'PENDING', FULFILLED:'FULFILLED', REJECTED:'REJECTED' } classDeferred{ constructor(callback){ this.status=STATUS.PENDING constresolve=()=>{ //TODO } constreject=()=>{ //TODO } try{ callback(resolve,reject) }catch(error){ //出现异常直接进行reject reject(error) } } }
这里还有个有意思的事情,早期浏览器的实现中fulfilled状态是resolved,明显与Promise规范不符。当然,现在已经修复了。
内部结果
除开状态,Promise内部还有个结果[[PromiseResult]],用来暂存resolve/reject接受的值。
继续在构造函数中添加一个内部结果。
classDeferred{ constructor(callback){ this.value=undefined this.status=STATUS.PENDING constresolve=value=>{ this.value=value //TODO } constreject=reason=>{ this.value=reason //TODO } try{ callback(resolve,reject) }catch(error){ //出现异常直接进行reject reject(error) } } }
储存回调
使用Promise的时候,我们一般都会调用promise对象的.then方法,在promise状态转为fulfilled或rejected的时候,拿到内部结果,然后做后续的处理。所以构造函数中,还需要构造两个数组,用来存储.then方法传入的回调。
classDeferred{ constructor(callback){ this.value=undefined this.status=STATUS.PENDING this.rejectQueue=[] this.resolveQueue=[] constresolve=value=>{ this.value=value //TODO } constreject=reason=>{ this.value=reason //TODO } try{ callback(resolve,reject) }catch(error){ //出现异常直接进行reject reject(error) } } }
resolve与reject
接下来,我们需要实现resolve和reject两个方法,这两个方法在被调用的时候,会改变promise对象的状态。而且任意一个方法在被调用之后,另外的方法是无法被调用的。
newPromise((resolve,reject)=>{ setTimeout(()=>{ resolve('