参考文章:深刻理解 Promise、【翻译】Promises/A+规范html
从入门Promise的正确姿式中咱们已经了解到Promise的基本用法。那么如今给你一个需求:根据Promise的用法和Promise/A+规范,实现一个本身的Promise函数。编程
根据Promise的用法咱们知道:在new一个Promise函数的时候,Promise函数必须接受一个函数做为参数。咱们暂且把这个参数函数称为执行器。数组
执行器提供两个参数(resolve,reject),且内部有状态机制(pending,resolved,rejected)。Promise构造函数的原型上有then方法。promise
咱们再来看一下PromiseA+标准中是怎样规定的:缓存
Pending
,在被 resolve
或 reject
时,状态变为 Fulfilled
或 Rejected
then
方法,且只接受两个函数参数 onFulfilled
、onRejected
由以上标准就容易就能实现这个类的大体结构。app
开始撸:异步
var PENDING = 'pending', RESOLVED = 'resolved', REJECTED = 'rejected', function Promise(executor) { //养成良好的编程习惯,先对参数作一个容错处理 if (executor && typeof executor != 'function') { throw new Error('Promise is not a function'); } let _this = this;//缓存this _this.status = PENDING;//当前Promise的状态 _this.value = undefined;//Promise成功执行时要传递给回调的数据,默认为undefined _this.reason = undefined;//Promise失败执行时传递给毁掉的缘由,默认为undefined function resolve(value) { //内置一个resolve方法 if (_this.status == PENDING) { _this.status = RESOLVED;//当调用resolve时,将Promise的状态改成resoled _this.value = value;//保存成功调用时传递进来的数据 } } function reject(reason) { if (_this.status == PENDING) { _this.status = REJECTED;//当调用reject时,将Promise的状态改成rejected _this.reason = reason;//保存失败调用时传递进来的缘由 } } executor(resolve,reject); } //then方法能够接受两个函数参数,分别表示当前Promise执行成功时的调用onFulfilled和执行失败时调用onRejected Promise.prototype.then = function(onFulfilled,onRejected) { let _this = this,//缓存this,保不齐后面会用到,固然若是你不想缓存this,也能够在后面使用箭头函数 if (_this.status == RESOLVED) { onFulfilled(_this.value); } if (_this.status == REJECTED) { onFulfilled(_this.reason); } }
看起来不错,但回调函数是当即执行的,也就是说上面实现的Promise只支持同步代码,而没法进行异步操做,好比这样是不行的函数
let p = new Promise(function(resolve, reject){ setTimeout(function(){ resolve('成功执行了!'); }, 1000) }) p.then(function(data){ console.log('成功', data) },function(err){ console.log('失败', err) }) // 不会输出任何代码
缘由是:咱们在then函数中只对成功态和失败态进行了判断,而实例被new时,执行器中的代码会当即执行,但setTimeout中的代码将稍后执行,也就是说,then方法执行时,Promise的状态没有被改变依然是pending态。post
因此咱们要对pending态也作判断,而因为代码多是异步的,因此回调函数就不应被当即执行,因此咱们就要想办法把回调函数进行缓存,当状态改变后(由pending态变成resolved或rejected态),再执行相应的函数。this
那么状态在何时改变呢:很显然,在执行resolve或rejected函数的时候,状态会发生改变。
而且,then方法是能够屡次使用的,因此要能存多个回调,那么这里咱们用一个数组来存储多个回调函数。
在实例上挂载两个参数
_this.onResolvedCallbacks = []; // 存放then成功的回调 _this.onRejectedCallbacks = []; // 存放then失败的回调
咱们再给then方法加一个pending时的判断
if(_this.status === 'pending'){ // 每一次then时,若是是等待态,就把回调函数push进数组中,何时改变状态何时再执行 _this.onResolvedCallbacks.push(function(){ // 这里用一个函数包起来,是为了后面加入新的逻辑进去 onFulfilled(_this.value) }) _this.onRejectedCallbacks.push(function(){ // 同理 onRjected(_this.reason) }) }
下一步要分别在resolve和reject方法里加入执行数组中存放的函数的方法,修改一下上面的resolve和reject方法
function resolve(value) { if (_this.status === 'pending') { _this.status = 'resolved' _this.value = value _this.onResolvedCallbacks.forEach(function(fn){ // 当成功的函数被调用时,以前缓存的回调函数会被一一调用 fn() }) } } function reject(reason) { if (_this.status === 'pending') { _this.status = 'rejected' _this.reason = reason _this.onRejectedCallbacks.forEach(function(fn){// 当失败的函数被调用时,以前缓存的回调函数会被一一调用 fn() }) } }
如今能够执行异步任务了,也能够给一个Promise函数屡次then了。
上面的代码看似很完美,前提是整个Promise函数可以正确执行,那样就没reject函数什么事了。但咱们必需要考虑的一种状况是:当Promise函数执行出错时怎么办,因此一个健壮的Promise函数是应该有错误处理机制的。因此咱们应该在Promise的代码体中加入try catch,若是出现异常,则捕捉错误交给reject。
咱们实现一下,思路很简单,在执行器执行时进行try catch
try{ executor(resolve, reject) }catch(e){ // 若是捕获发生异常,直接调失败,并把参数穿进去 reject(e) }
上面说过了,then能够链式调用,也是这一点让Promise十分好用,固然这部分源码也比较复杂
咱们知道jQuery实现链式调用是return了一个this,但Promise不行,为何不行?
由于then函数内返回的是一个新的Promise对象。咱们看一下标准是如何定义的:
标准中规定:
then
方法必须返回一个新的 Promise实例
(ES6中的标准,Promise/A+中没有明确说明)
then
中回调的执行顺序,onFulfilled
或 onRejected
必须异步调用因此咱们要作的就是:在then方法中先定义一个新的Promise,取名为promise2,而后在三种状态下分别用promise2包装一下,在调用onFulfilled时用一个变量x接收返回值,try catch一下代码,没错就调resolve传入x,有错就调reject传入错误,最后再把promise2给return出去,就能够进行链式调用了
//修改then Promise.prototype.then = function(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) {return vaule}; onRejected = typeof onRejected === 'function' ? onFulfilled : function(err) {throw err}; let _this = this;//缓存this,保不齐后面会用到,固然若是你不想缓存this,也能够在后面使用箭头函数 let promise2; if (_this.status === RESOLVED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函数的执行状况要考虑多种状况,后面会细说 let x = onFulfilled(_this.value);//将执行onFulfilled的返回值传给x,这里须要注意的是执行过程当中有可能会出错 resolve(x); } catch(e) { reject(e); } }) } if (_this.status === REJECTED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函数的执行状况要考虑多种状况,后面会细说 let x = onRejected(_this.reason);//将执行onFulfilled的返回值传给x(即此次then函数执行的返回值),这里须要注意的是执行过程当中有可能会出错 resolve(x);//这个x会被下一次的then函数接收到 } catch(e) { reject(e); } }) } if(_this.status === PENDING){ promise2 = new Promise(function(resolve,reject) { // 每一次then时,若是是等待态,就把回调函数push进数组中,何时改变状态何时再执行 _this.onResolvedCallbacks.push(function(){ // 这里用一个函数包起来,是为了后面加入新的逻辑进去 try { let x = onFulfilled(_this.value); resolve(x); } catch(e) { reject(e); } }) _this.onRejectedCallbacks.push(function(){ // 同理 try { let x = onRejected(_this.reason); resolve(x); } catch(e) { reject(e); } }) }) } }
上面的实现虽然能用,可是很粗糙。
在接下的分析以前,我但愿你们可以清晰的明白x的值表明什么:x表示的是上一次的then函数或promise函数执行结果的返回值,这个x的值会被resolve(x),做为下一次then函数调用时的参数。
明确了x的值之后,接下来对onFulfilled和onRejected函数可能出现的状况作一个列举,这也是为了咱们的Promise函数能作到最大的容错率:
var p1 = p.then(function(){// 这里得用var,let因为做用域的缘由会报错undefined return p1 })
5.前一次then返回的是一个别人本身随便写的Promise,这个Promise多是个有then的普通对象,好比{then:'哈哈哈'},也有可能在then里故意抛错(这种蛋疼的 操做咱们也要考虑进去)。好比他这样写
let promise = {} Object.defineProperty(promise,'then',{ value: function(){ throw new Error('报错气死你') } }) // 若是返回这东西,咱们再去调then方法就确定会报错了
6.调resolve的时候再传一个Promise下去,咱们还得处理这个Promise。
p.then(function(data) { return new Promise(function(resolve, reject) { resolve(new Promise(function(resolve,reject){ resolve(1111) })) }) })
7.可能既调resolve又调reject,得忽略后一个。
8.光then,里面啥也不写。
问题8最好解决,咱们只要手动添加一个onFulfilled和onRejected进去就行了。
对于问题1-7,咱们能够采起统一的以为方案,定义一个函数来判断和处理这一系列的状况,官方给出了一个叫作resolvePromise的函数。
因此,咱们进一步来完善then方法。
Promise.prototype.then = function(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) {return value}; onRejected = typeof onRejected === 'function' ? onRejected : function(err) {throw err};
//这里正好解释了在正式的Promise函数中,咱们为何能够不写onRejected函数,
//由于then方法内部会帮咱们封装好一个onRejected函数,用来抛出上一次then或Promise执行出错的信息,这也是为何能够在最后执行catch方法的缘由 let _this = this;//缓存this,保不齐后面会用到,固然若是你不想缓存this,也能够在后面使用箭头函数 let promise2; if (_this.status === RESOLVED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函数的执行状况要考虑多种状况,后面会细说 let x = onFulfilled(_this.value);//将执行onFulfilled的返回值传给x,这里须要注意的是执行过程当中有可能会出错 resolvePromise(promise2,x,resolve,reject); } catch(e) { reject(e); } }) } if (_this.status === REJECTED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函数的执行状况要考虑多种状况,后面会细说 let x = onRejected(_this.reason);//将执行onFulfilled的返回值传给x(即此次then函数执行的返回值),这里须要注意的是执行过程当中有可能会出错 resolvePromise(promise2,x,resolve,reject);//这个x会被下一次的then函数接收到 } catch(e) { reject(e); } }) } if(_this.status === PENDING){ promise2 = new Promise(function(resolve,reject) { // 每一次then时,若是是等待态,就把回调函数push进数组中,何时改变状态何时再执行 _this.onResolvedCallbacks.push(function(){ // 这里用一个函数包起来,是为了后面加入新的逻辑进去 try { let x = onFulfilled(_this.value); resolvePromise(promise2,x,resolve,reject); } catch(e) { reject(e); } }) _this.onRejectedCallbacks.push(function(){ // 同理 try { let x = onRejected(_this.reason); resolvePromise(promise2,x,resolve,reject); } catch(e) { reject(e); } }) }) } return promise2; }
定义resolvePromise方法:
function resolvePromise(promise2, x, resolve, reject) { // 接受四个参数: 新的Promise、返回值,成功和失败的回调 // 有可能这里返回的x是别人的promise // 尽量容许其余乱写 if (promise2 === x) { //这里应该报一个类型错误,来解决问题4 return reject(new TypeError('循环引用了')) } // 看x是否是一个promise,promise应该是一个对象 let called = false; // 表示是否调用过成功或者失败,用来解决问题7 //下面判断上一次then返回的是普通值仍是函数,来解决问题一、2 if (x !== null && (typeof x === 'object' || typeof x === 'function')) { // 多是promise {},看这个对象中是否有then方法,若是有then我就认为他是promise了 try { let then = x.then;// 保存一下x的then方法 if (typeof then === 'function') { // 成功 //用call方法修改指针为x,不然this指向window then.call(x, function (y) {//若是x是一个Promise对象,y参数表示x执行后的resolve值 //console.log(y); if (called) return //若是调用过就return掉 called = true // y可能仍是一个promise,在去解析直到返回的是一个普通值 resolvePromise(promise2, y, resolve, reject)//递归调用,解决了问题6 }, function (err) { //失败时执行的函数 if (called) return called = true console.log('r'); reject(err); }) } else {//若是x不是一个Promise对象,则直接resolve(x) resolve(x) } } catch (e) { if (called) return called = true; reject(e); } } else { // 说明是一个普通值 resolve(x); // 表示成功了 } }
因为catch方法是then(null, onRejected)的语法糖,因此这里也很好实现。
Promise.prototype.catch = function(onRejected) { return this.then(null,onRejected); }
至此,一个简易粗糙的Promise函数已经实现了。同窗们能够本身用本身写的这个Promise函数验证一下是不是否可行。