【javascript】手写Promise分步骤超详尽解答(超越PromiseA+标准)

背景


一、实现同步功能

  • 首先需要写出大致框架和同步功能。
  • 代码如下:
function myPromise(executer){
    this.state='pending';
    this.value=undefined;
    this.reason=undefined;
    let that = this;
    function resolve(value){
        that.state='resolved'
        that.value=value;
    };
    function reject(reason){
        that.state='rejected'
        that.reason = reason;
    }
    try{
        executer(resolve,reject)
    }catch(e){
        reject(e)
    }
}
myPromise.prototype.then=function(onFullfilled,onRejected){
    if(this.state==='pending'){
    }
    if(this.state==='resolved'){
        onFullfilled(this.value)
    }
    if(this.state==='rejected'){
        onRejected(this.reason)
    }
}
let f= new myPromise(function(resolve,reject){
    resolve('value')
}).then(res=>console.log(res))

代码解读

  • 先使用es5写法,便于大家理解,最后放es6写法。
  • 首先Promise是个函数,它需要传一个function,就是excuter,就相当于我们使用Promise时那个new Promise(function(resolve,reject))中的function()。
  • 自然这个function有2个参数,一个是resolve,一个是reject,所以需要在这个函数里写一个resolve和reject函数,供excuter执行。代码中写了resolve(‘value’)所以excuter执行后会执行函数里的resolve()。
  • promise有三种状态,这个不用说了,所以写初始状态pending,这里得用this,才能继承给new。
  • 传值还需要给resolve初始值和reject初始值undefined。
  • 在resolve执行,给state换成相应状态,值换成相应值。
  • 为什么要绑个that?因为resolve是excuter执行,也就是function(resolve,reject)函数体内的this,this是global,this会丢失,取不到内部状态。
  • 再看看then:
  • 由于new promise后需要使用.then,所以需要在promise.prototype上增加then方法。有人问为啥不加到Function.prototype上?因为new出来的是对象而不是方法,对象沿着原型链向上查询方法不会经过Function,如果加Function上,不New的话只用Promise倒是可以直接.then。
  • 这个.then前面是挂在promise.prototype下的,这里this就是promise内部。所以列出三种状态和三种情况,pending情况暂时不讨论,resolve和reject是一样的,状态是resolve那么就执行then的onFullfilled,也就是.then当中的第一个参数res=>console.log(res)。这个res就是onFullfilled(this.value)当中的this.value。
  • 为什么要写个try和catch?因为执行new myPromise(executer)的时候也可能报错这时候就返回reject就行了。有人可能问,.then中干嘛不加trycatch?因为.then中如果报错那么就需要下一个.then才能捕获异常,而不是应该本次本来进onFullfilled然后跳onRejected。

二、实现异步功能

  • 刚才这个只能对同步执行的有效,那么如何实现异步也有效呢?
  • 这个就跟前面几篇文章的小例子一个意思,先把将要执行的方法存到数组里,然后在异步完成后执行数组里存的函数。(不使用数组执行待更新)
function myPromise(executer){
    this.state='pending';
    this.value=undefined;
    this.reason=undefined;
    let that = this;
    this.resolveArr=[];
    this.rejectArr =[];
    function resolve(value){
        if(that.state==='pending'){
            that.state='resolved'
            that.value=value;
            that.resolveArr.forEach(fn=>fn())
        }
    };
    function reject(reason){
        if(that.state==='pending'){
            that.state='rejected'
            that.reason = reason;
            that.rejectArr.forEach(fn=>fn())
        }
    }
    try{
        executer(resolve,reject)
    }catch(e){
        reject(e)
    }
}
myPromise.prototype.then=function(onFullfilled,onRejected){
    if(this.state==='pending'){
        this.resolveArr.push(()=>onFullfilled(this.value))
        this.rejectArr.push(()=>onRejected(this.reason))
    }
    if(this.state==='resolved'){
        onFullfilled(this.value)
    }
    if(this.state==='rejected'){
        onRejected(this.reason)
    }
}
let f= new myPromise(function(resolve,reject){
    setTimeout(() => {
        resolve('value')
    }, 1000);
}).then(res=>console.log(res))
let p= new myPromise(function(resolve,reject){
    resolve('ddd')
}).then((res)=>console.log(res))

代码解读

  • 这地方难点在执行顺序上,我们先梳理下执行顺序。如果是异步操作,异步操作完成后会调用resolve函数,也就是promise里的resolve执行会在异步完成后直接调用。我们在.then后增加的onFullfilled函数,也就是(res)=>console.log(res),这个函数Push进promise内部数组去,等异步完成后执行其resolve函数后会顺带把数组里的函数拿出来执行,这样就完成了在异步后调用的效果。
  • 注意push的这个函数其实就是利用柯里化的思想,并没有立即执行,等待时机去执行。
  • 有人会问为什么要写if (this.state==='pending')?这个问题好。我们都知道promise的状态是只要改变后就不会再变回来,也就是只要变成resolve就不会变成reject或者变回pending,我们在内部有个try{}catch(),如果在new一个Promise时中间写function(resolve,reject){resolve();throw new Error()}那么它岂不是先状态变成成功,然后被trycatch捕获异常又把状态变成reject?,所以这里限定Pending后状态就不会随意改变了。
  • 我们再梳理下同步执行顺序,如果是同步操作,excuter立即执行resolve操作,将状态改变,到.then处状态变为resolve或者reject,立即执行.then的操作。这个没什么毛病。
  • 有人可能问,为什么要数组存着呢?直接放空函数,等异步调用直接赋成新函数不是也可以吗?这个问题我也思考过,实测这种情况会有852通过20失败,报错为then may be called multiple times on the same promise.。也就是说类似下面这种情况就会给一个实例叠加多个function:
let p = new  myPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve(11)
        }, 11000);
})
p.then('xx');
p.then('ii')

三、实现链式调用功能

  • 很多地方说最难的是链式调用,实际链式调用并不难,最难的在后面,链式调用只要返回一个promise即可链式调用。
function myPromise(executer){
    this.state='pending';
    this.value=undefined;
    this.reason=undefined;
    let that = this;
    this.resolveArr=[];
    this.rejectArr =[];
    function resolve(value){
        if(that.state==='pending'){
            that.state='resolved'
            that.value=value;
            that.resolveArr.forEach(fn=>fn())
        }
    };
    function reject(reason){
        if(that.state==='pending'){
            that.state='rejected'
            that.reason = reason;
            that.rejectArr.forEach(fn=>fn())
        }
    }
    try{
        executer(resolve,reject)
    }catch(e){
        reject(e)
    }
}
myPromise.prototype.then=function(onFullfilled,onRejected){
    let that = this;
    let promise2 =new myPromise(function(resolve,reject){
        if(that.state==='pending'){
            that.resolveArr.push(()=>{
                try{
                    let x = onFullfilled(that.value)
                    resolve(x);
                }catch(e){
                    reject(e)
                }
            })
            that.rejectArr.push(()=>{
                try{
                    let x = onRejected(that.reason)
                    resolve(x)
                }catch(e){
                    reject(e)
                }  
            })
        }
        if(that.state==='resolved'){
            try{
                let x =  onFullfilled(that.value)
                resolve(x);
            }catch(e){
                reject(e);
            }
        }
        if(that.state==='rejected'){
            try{
                let x = onRejected(that.reason)
                resolve(x);
            }catch(e){
                reject(e);
            }
        }
    })
    return promise2
}
let f= new myPromise(function(resolve,reject){
    setTimeout(() => {
        resolve('value')
    }, 1000);
}).then(res=>console.log(res)).then(res=>console.log(res))
let p=new myPromise(function(resolve,reject){
    resolve('xxx')
}).then(res=>console.log(res)).then(res=>console.log(res));

代码解读

  • 链式调用就是.then后面还能接.then,jq也是能链式的,它的实现就是return一个this,但是为什么Promise不能return一个this呢?因为promise状态不可变,再返回this实际就是这个Promise状态已经变更了,所以需要new一个新的promise再return出来给下一个.then。
  • 所以我们在.then里面new 一个新Promise,叫Promise2,并且要绑定下this,因为在Promise2里面的this就会变为global,另外,我们把.then的resolve(res)和reject(res)抽离出来,变成x,也便于理解,因为x的返回值就是.then中的返回值。执行x就是执行then后面的函数。
  • 那么,为什么要在onRejected里执行resolve呢?因为就算这个.then的Promise走了reject,那么这个reject没出错,下一个.then就是resolve,这也是Promise的性质。
  • 为什么要加trycatch?其实我们在实现同步功能的最后一小点已经做出了解释,这个其实也相当于第一个Promise中的try/catch/excuter(resolve,reject)。
  • 有人可能会问,如果每个条件下面写return new Promise 行不行?就相当于下面的代码:
  if(that.status==='pending'){
    return new myPromise(function(resolve,reject){
        try{
          that.resolveArr.push(()=>resolve(onFullfilled(that.value)))
        }
        catch(e){
            that.rejectArr.push(()=>reject(onRejected(e)))
          }
        }
    )}
  • 这种写法其实也是可以的,同样也是返回了一个新promise,后面也可以跟.then。

四、实现返回值类型判断

  • 这是promise当中最难的地方,也是当初我各种找资料困扰了很久的地方。这里我尽量写详细点。
  • 到现在为止,我们写的promise有几个缺陷,其中最大的缺陷就是.then的返回值也就是x,它也有可能是个promise,或者是甚至是promise里包个promise,正统的promise会把then里返回值的Promise全部拆解开来调用下一个.then,而我们写的promise完全不会拆开,直接返回个promise{ < pending> }过来。
  • 测试例子:可以拿去试验下:
let f= new myPromise(function(resolve,reject){
    setTimeout(() => {
        resolve('value')
    }, 1000);
}).then(res=>new myPromise((resolve,reject)=>{setTimeout(() => {
    resolve(res)
}, 1000);})).then(res=>console.log(res))

myPromise {
state: ‘pending’,
value: undefined,
reason: undefined,
resolveArr: [],
rejectArr: [] }

  • 原版对照:
let f= new Promise(function(resolve,reject){
  setTimeout(() => {
      resolve('value')
  }, 1000);
}).then(res=>new Promise((resolve,reject)=>{setTimeout(() => {
  resolve(res)
}, 1000);})).then(res=>console.log(res))
let p= new Promise(function(resolve,reject){
  setTimeout(() => {
      resolve('1111')
  }, 1000);
}).then(res=>new Promise((resolve,reject)=>{setTimeout(() => {
  resolve(new Promise((resolve,reject)=>{
      setTimeout(() => {
          resolve(res)
      }, 1000);
  }))
}, 1000);})).then(res=>console.log(res))

value
1111

  • 另外还有些细节方面,比如实例不能是自身:
let p= new myPromise(function(resolve,reject){
  resolve('xx')
})
let p2=p.then(res=>p2)
p2.then(res=>console.log(res),res=>console.log(res))
  • 原版对照:
let p= new Promise(function(resolve,reject){
    resolve('xx')
})
let p2=p.then(res=>p2)
p2.then(res=>console.log(res),res=>console.log(res))

TypeError: Chaining cycle detected for promise #<Promise>

  • 目前的promise也是无法通过promiseA+的。
  • 下面开始一步步解释,我个人觉得后面的思路正常人都想不到,不知道当初创建Promise的人咋想出来的。
  • 首先因为x有可能是个promise返回值,所以我们新建个函数resolvePromise替换原来的resolve(x)。
  • resolvePromise为什么有promise2,x,resolve,reject4个参数?有promise2是为了后面判断我们这个x它不是promise2,否则像原版就会报 TypeError: Chaining cycle detected for promise。x就是为了类型判断是不是Promise或者Promise里再包着Promise。resolve和reject是为了处理满足什么条件跳resolve,什么条件跳reject。
  • 我们先将then函数改写成这样:
myPromise.prototype.then=function(onFullfilled,onRejected){
    let that = this;
    let promise2 =new myPromise(function(resolve,reject){
        if(that.state==='pending'){
            that.resolveArr.push(()=>{
                try{
                    let x = onFullfilled(that.value)
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e)
                }
            })
            that.rejectArr.push(()=>{
                try{
                    let x = onRejected(that.reason)
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e)
                }  
            })
        }
        if(that.state==='resolved'){
            try{
                let x =  onFullfilled(that.value)
                resolvePromise(promise2,x,resolve,reject)
            }catch(e){
                reject(e);
            }
           
        }
        if(that.state==='rejected'){
            try{
                let x = onRejected(that.reason)
                resolvePromise(promise2,x,resolve,reject)
            }catch(e){
                reject(e);
            }
        }
    })
    return promise2
}
function resolvePromise(promise2,x,resolve,reject){
    resolve(x)
}
  • 有人会问,为什么catch里的reject不用resolvePromise处理?这个原版本来就是这样,失败了再去判断就没什么意义了。 所以在resolvePromise函数里,有些特殊情况,也就直接reject处理。
  • 有人会问,你在resolvePromise中传入promise2,这个promise2是最后返回出来的,还没返回就能取到?这个问题问的好。我一开始看见这个操作感觉真尼玛牛b,还没返回的东西还能取到值?这个是什么骚操作?这个东西就涉及js的event loop概念,简单说就是js中有些函数是异步操作的,js会给这些异步操作搞一个队列,当然不同环境event loop略微有点不一样。先看一个例子:
console.log(1);
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});
new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})
setTimeout(() => {
  console.log(6);
})
console.log(7);
  • nodejs中运行结果:

1
4
7
5
2
6
3

  • 浏览器中运行结果:

1
4
7
5
2
3
6

  • 解释下原因:event loop中有3个东西,一个叫全局脚本,一个叫宏队列,一个叫微队列,宏队列和微队列都是异步队列,浏览器中有一个宏队列和一个微队列,Nodejs中有4个宏队列和2个微队列。一般来说,会先执行全局脚本,中间碰上异步的该插入宏队列插入宏队列,插入微队列插入微队列,全局执行完,先开始执行微队列,再开始执行宏队列。
  • 在nodejs中与浏览器中,我们发现最后的36和63顺序不一样。settimeout无论在哪个环境中都属于宏队列,而微队列是率先执行的,个人猜想,真正的Promise在nodejs和浏览器中的.then处实现略有区别,nodejs中先出6再出3,个人觉得很可能是.then相当于又增加一个宏队列插入到下方那个settimeout宏队列后面,导致这个结果。而浏览器中的.then则是加入的是微队列,比下方settimeout宏队列率先执行。但这个浏览器目前插入微队列是只有Object.observe MutationObserver这2方法,实在想不出如何做到的,Nodejs倒是可以用proccess.nexttick()来实现插入微队列,但nodejs上的.then明显是宏队列,用settimeout即可。
  • 有人可能要问,如果微队列又生成个微队列岂不是宏队列永远无法执行了?这个问题别人早考虑了,所以这个微队列递归调用还有个次数上限。
  • 于是我们继续修改代码:
myPromise.prototype.then=function(onFullfilled,onRejected){
    let that = this;
    let promise2 =new myPromise(function(resolve,reject){
        if(that.state==='pending'){
            that.resolveArr.push(()=>{
                setTimeout(() => {
                    try{
                        let x = onFullfilled(that.value)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                });
            })
            that.rejectArr.push(()=>{
                setTimeout(() => {
                    try{
                        let x = onRejected(that.reason)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }  
                });
            })
        }
        if(that.state==='resolved'){
            setTimeout(() => {
                try{
                    let x =  onFullfilled(that.value)
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e);
                }
            }); 
        }
        if(that.state==='rejected'){
            setTimeout(() => {
                try{
                    let x = onRejected(that.reason)
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e);
                }  
            });
        }
    })
    return promise2
}
function resolvePromise(promise2,x,resolve,reject){
    console.log(promise2,x,resolve,reject)
    resolve(x)
}
  • 将所有处理上全加上settimeout来达到效果,resolvepromise时可以看一下是否4个值已全部取到。
  • 还记得前面那个x等于promise2原版产生的报错吗?那么就在resolvePromise中比较一下,如果相等就抛出异常。
  • 做完这个,该判断x到底是不是promise了,但promise情况比较复杂,我们先判断是常量还是一个obj或者function。这里有人会问为啥判断是不是function?所有promise new出来不是obj吗?其实这是作者为了套接某些函数具有相同性质的then方法所弄的,感觉做这种给别人使用的东西就必须很要有这种思想啊,一般人直接判断是不是Obj,不是Obj就直接给你返回就完事了。
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => resolve(y), r => reject(r))
            } else {
                resolve(x)
            }
        } catch (e) {
            reject(e)
        }
    } else {
        resolve(x)
    }
}
  • 这个null去看类型也是obj,所以要把Null给排除掉,有Null也直接抛出即可。
  • 有人问为什么这里要加trycatch?因为我们取then的时候有可能报错,比如传进来的东西它原本代码是这么写的:
let obj={};
Object.defineProperty(obj,'then',{
	get(){
		throw new Error('xxx')
	}
})
  • 那么有人问为什么写then.call(x,y=>resolve(y),r=>reject(r))?这种跟直接x.then(y=>resolve(y),(r)=>reject(r))有区别吗?答案是当然有区别,因为这样不会重复取两次,还记得前面有篇文章说的调用指定次数后执行的函数吗?结合上面的例子,调用2次后throw个error不就又挂了。
  • 这些类型检测一般正常人想不到,个人觉得promise作者是想去兼容别人写的then所留的缺口,否则直接进行判断一棍子打死不就完了?
  • 下面还有个难点,如果promise里面还包个promise那么如何处理?这里就需要递归了:
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
    }
    let called;
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if(called)return;
                    called=true;
                    resolvePromise(promise2,y,resolve,reject)
                }, r => {
                    if(called)return;
                    called=true;
                    reject(r);
                })
            } else {//常量
                resolve(x)
            }
        } catch (e) {
            if(called)return;
            called=true;
            reject(e)
        }
    } else {//常量
        resolve(x)
    }
}
  • 这个地方特别烧脑,先说一下测试例子,then()里面是new 一个promise,然后promise里面resolve一个new promise。就是前面讲的嵌套promise那个例子。这么看来,y就是那个嵌套的第二个new promise,目标就是把y进行层层解析,只要是Promise就递归下去,直到返回出常量或者函数。其实Promise主要看.then。x是promise进来,运行y就是x的promise.then,那么y进入递归进入下一层,也就跟x一样,始终能拆成resolve一个常量。
  • 另外为什么要加上called?因为这个promise可能是别人写的,这就导致一开始在第一节解释pending说的一个问题,有可能走完resolve又回来走reject的情况,如果使用called的话,就保证走完resolve不会跳回来走reject了,而如果是常量,那么就不存在成功或者失败的问题。
  • 这个时候可以拿这个例子测试下,应该能正常打印出来了:
let p= new myPromise(function(resolve,reject){
    setTimeout(() => {
        resolve('1111')
    }, 1000);
}).then(res=>new myPromise((resolve,reject)=>{setTimeout(() => {
    resolve(new myPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve(res)
        }, 1000);
    }))
}, 1000);})).then((res)=>console.log(res))
  • 你以为结束了吗?还没完,目前的promise拿去测试是831通过41失败。
  • 下面还需要解决then的透传问题,看测试例子:
let p= new myPromise(function(resolve,reject){
    setTimeout(() => {
        resolve('1111')
    }, 1000);
}).then().then((res)=>console.log(res))
  • 什么叫透传?就是第一个then没传东西,下一个then继续处理。我们的promise运行这个测试例子啥都不会打印,而正版的就能打印出1111来。
  • 其实原理很简单,就是如果没传值的话,就return参数即可把函数传给下一个.then。
  • 这里有个点需要注意下:走reject话不能return错误,而是应该throw错误,让后一个.then也走reject。
  • 我们就在前面加上2句:
myPromise.prototype.then = function (onFullfilled, onRejected) {
    let that = this;
    onFullfilled=typeof onFullfilled ==='function'?onFullfilled:val=>val;
    onRejected=typeof onRejected ==='function'?onRejected:err=>{throw err};
    let promise2 = new myPromise(function (resolve, reject) {
        if (that.state === 'pending') {
            that.resolveArr.push(() => {
                setTimeout(() => {
                    try {
                        let x = onFullfilled(that.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                });
            })
            that.rejectArr.push(() => {
                setTimeout(() => {
                    try {
                        let x = onRejected(that.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                });
            })
        }
        if (that.state === 'resolved') {
            setTimeout(() => {
                try {
                    let x = onFullfilled(that.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e);
                }
            });
        }
        if (that.state === 'rejected') {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e);
                }
            });
        }
    })
    return promise2
}
  • 到此处再去执行promiseA+测试就能872全通过了!
  • 但是目前这个Promise还有Bug!
  • 我们前面用递归解决了.then中返回的promise里resolve再嵌套Promise的的情况,但是没有解决第一个Promise里resolve嵌套Promise的情况。看下面这个测试例子:
let p= new myPromise(function(resolve,reject){
    setTimeout(() => {
        resolve(new myPromise((resolve,reject)=>{
            setTimeout(() => {
                resolve('11111')
            }, 1000);
        }))
    }, 1000);
}).then((res)=>console.log(res))

myPromise {
state: ‘pending’,
value: undefined,
reason: undefined,
resolveArr: [],
rejectArr: [] }

  • 可以发现我们的Promise打印出来就是个对象,而正版的就会把1111给全输出。
  • 这里的思路跟resolvePromise的思路一样,也是使用递归。
function myPromise(executer) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    let that = this;
    this.resolveArr = [];
    this.rejectArr = [];
    function resolve(value) {
        if(value instanceof myPromise){
            return value.then(resolve,reject)
        }
        if (that.state === 'pending') {
            that.state = 'resolved'
            that.value = value;
            that.resolveArr.forEach(fn => fn())
        }
    };
    function reject(reason) {
        if (that.state === 'pending') {
            that.state = 'rejected'
            that.reason = reason;
            that.rejectArr.forEach(fn => fn())
        }
    }
    try {
        executer(resolve, reject)
    } catch (e) {
        reject(e)
    }
}
  • 主要就是看resolve(value)中的value是不是promise,如果是promise就解析then,当然它这个还有个bug,如果这个value是别人的promise实例照样打印不出来,不过这个如果要进行判断就比较麻烦了。

五、实现catch方法

  • 这个catch比较简单,其实是then(null,callback)的语法糖。可以照着then一样写个函数:
myPromise.prototype.catch=function(errcallback){
    return this.then(null,errcallback);
}

六、实现promise.resolve/reject方法

  • 这个是直接调用类名而不是实例对象的操作,所以是静态方法:
myPromise.resolve=function(value){
    return new myPromise((resolve,reject)=>{
        resolve(value)
    })
}
myPromise.reject=function(reason){
    return new myPromise((resolve,reject)=>{
        reject(reason)
    })
}

七、实现promise.all方法

  • 这个就是传一个数组,所有都执行完就返回数组。
  • 这个也要进行类型判断,不过经过前面resolvePromise函数的编写,这个地方怎么判断感觉都会有bug。于是就随意点,自由发挥,当然也可以写是promise实例什么的。
myPromise.all= function (promises) {
    function isPromise(x){
        if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
            try{
                let s= typeof  x.then ==='function';
                return s;
            }catch(e){
                return false
            }
        }else{
            return false
        }
    }
    return new myPromise((resolve,reject)=>{
        let arr = [];
        let p=0;
        let proccessData=(index,data)=>{
            arr[index]=data;
            p++;
            if(p===promises.length){
                resolve(arr)
            }
        }
        for(let i=0;i<promises.length;i++){
            let current= promises[i];
            if(isPromise(current)){
                current.then(data=>{
                    proccessData(i,data)
                },reject)
            }else{
                proccessData(i,current)
            }
        }
    })
}
  • 这里面一个坑就是最终这个promise的resolve条件上,由于异步完成才会调用proccessdata函数,所以我们不能写index===promises.length,而应该重新声明个变量,每有调用加一即可。
  • 测试例子:
let p =function () {
    return new  myPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve(11)
        }, 11000);
    })
}
let f =function () {
    return new  myPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve(22)
        }, 12000);
    })
}
myPromise.all([1,p(),2,f(),3]).then(res=>console.log(res))

[ 1, 11, 2, 22, 3 ]


八、实现ES6写法

  • ES6写法就稍微改变下,剩下的就是复制粘贴。
  • 其中then方法由于在class类里其实是可以不需要绑定this。
  • 另外就是分清楚静态方法原型方法复制粘贴的位置即可。
  • resolvePromise函数跟上面一模一样,都不用改。
  • 已测试可以通过A+。
class myPromise{
    constructor(executer){
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        let that = this;
        this.resolveArr = [];
        this.rejectArr = [];
        function resolve(value) {
            if(value instanceof myPromise){
                return value.then(resolve,reject)
            }
            if (that.state === 'pending') {
                that.state = 'resolved'
                that.value = value;
                that.resolveArr.forEach(fn => fn())
            }
        };
        function reject(reason) {
            if (that.state === 'pending') {
                that.state = 'rejected'
                that.reason = reason;
                that.rejectArr.forEach(fn => fn())
            }
        }
        try {
            executer(resolve, reject)
        } catch (e) {
            reject(e)
        } 
    }
    then(onFullfilled,onRejected) {  
        let that = this;
        onFullfilled=typeof onFullfilled ==='function'?onFullfilled:val=>val;
        onRejected=typeof onRejected ==='function'?onRejected:err=>{throw err};
        let promise2 = new myPromise(function (resolve, reject) {
            if (that.state === 'pending') {
                that.resolveArr.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFullfilled(that.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    });
                })
                that.rejectArr.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(that.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    });
                })
            }
            if (that.state === 'resolved') {
                setTimeout(() => {
                    try {
                        let x = onFullfilled(that.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                });
            }
            if (that.state === 'rejected') {
                setTimeout(() => {
                    try {
                        let x = onRejected(that.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                });
            }
        })
        return promise2  
    }
    catch(errcallback){
        return this.then(null,errcallback);
    }
    static resolve(value){
        return new myPromise((resolve,reject)=>{
            resolve(value)
        })
    }
    static reject(reason){
        return new myPromise((resolve,reject)=>{
            reject(reason)
        })
    }
}
let p = new  myPromise((resolve,reject)=>{
        setTimeout(() => {
            reject(11)
        }, 11000);
})
p.then('xx').catch((e)=>console.log(e));
myPromise.resolve('111').then((res)=>console.log(res));

九、测试promiseA+方法

  • 这个是为防止有人不知道额外加的。
  • 首先npm安装promises-aplus-tests -g
  • 使用promises-aplus-tests 你的文件名.js
  • 文件里需要这么导出:
myPromise.deferred = function () {
    let dfd={}
    dfd.promise = new myPromise((resolve,reject)=>{
        dfd.resolve=resolve;
        dfd.reject=reject;
    })
    return dfd
}
module.exports = myPromise;
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值