根据 Promises/A+ 手写 Promsie

前言

上一篇文章 JavaScript Promise基础(对于 Promise用法不熟悉的,可以先看这篇文章,理解了再来看这篇文章,会对你有很大帮助)中,介绍了Promise的基本使用,在这篇文章中,我们将根据 Promises/A+ 规范试着自己来写一个Promise,主要是学习Promise的内部机制与它的编程思想。

Promise到底是什么?

Promise到底是啥玩意呢?是一个类、数组、对象、函数?好了不猜了,打印出来看看吧console.dir(Promise),是骡子是马拉出来遛遛呗!

呀呀呀!原来 Promise 是一个构造函数,自己身上有 all、resolve、reject,原型上有 then、catch 等眼熟的方法。那就不用废话了 new 一个玩玩呗!

let p=new Promise((resolve,reject)=>{
    console.log(1);
    resolve('成功');
    reject('失败');
});
console.log(2);
p.then((data)=>{
    console.log(data+1);
},(err)=>{
    console.log(err);
})
p.then((data)=>{
    console.log(data+2);
},(err)=>{
    console.log(err);
})
复制代码

输出 1 2 成功1 成功2

new Promise时传递一个函数(executor执行器是立即执行的),并接收两个参数:resolve,reject,分别表示成功的回调函数与失败的回调函数。每一个实例都有一个 then 方法,参数是成功和失败,成功会有成功的值,失败会有失败的原因,并且成功就不能失败反之也一样。同一个 Promise 可以多次 then……貌似跑偏了呀!你们都懂也应该会用,还是回到主题开始自己实现吧!

1、实现基本的 Promise

class Promise{
    constructor(executor){
        this.status='pending';  //默认的状态
        this.value=undefined;   //默认成功的值
        this.reason=undefined;  //默认失败的原因
        this.onResolvedCallbacks=[];//存放成功的数组
        this.onRejectedCallbacks=[];//存放失败的数组
        let resolve=(value)=>{
            if(this.status==='pending'){//这里的判 断是为了防止executor中调用两次resovle或reject方法
               this.status='resolved';//成功了 
                this.value=value;//成功的值
            }
        }
        let reject=(reason)=>{
            if(this.status==='pending'){
                this.status='rejected';//失败了
                this.reason=reason;//失败的原因
            }
        }
        try {//捕获异常
            executor(resolve,reject);//默认让执行器执行
        } catch (err) {
            reject(err)
        } 
    }
    then(onFufilled,onRejected){    
        if(this.status==='resolved'){
            onFufilled(this.value);
        }
        if(this.status==='rejected'){
            onRejected(this.reason);
        }
    }
}

let p=new Promise((resolve,reject)=>{
    resolve('成功');
    //reject('失败');
})
p.then((data)=>{
    console.log(data);
},(err)=>{
    console.log(err);
})
复制代码
  • executor:实例化 Promise 对象时传入的参数,即 (resolve,reject)=>{ }
  • status:Promise的状态,默认为 pendding 态,每当调用 resolve 或 reject 方法时,就会改变值,在后面的 then 方法中会用到
  • value:resolve回调成功的值
  • reason:reject回调成功的值
  • resolve:成功执行的函数,执行时传入的参数会作为 then 方法中第一个回调函数的参数
  • reject:失败执行的函数,执行时传入的参数会作为 then 方法中第二个回调函数的参数

执行输出成功,那么问题来了,我们费劲半天写 Promise 上来就执行有啥用?使用 Promise 时我们一般会写一些异步代码,等到异步操作执行完才会触发 resolve 或者 reject 函数。可是现在当执行 then 方法的时候此时的状态还是初始的pending 状态,所以为了能取到参数,我们可以通过发布订阅模式来实现。

2、异步处理

then 方法里添加如下代码,当状态为 pending 时,我们先把回调函数存到对应的数组里,等待调用。

if(this.status==='pending'){
    this.onResolvedCallbacks.push(()=>{
        onFufilled(this.value);
    });
    this.onRejectedCallbacks.push(()=>{
        onRejected(this.reason);
    })
}
复制代码

resolve 和 reject 方法里分别添加如下代码,当调用的时候,把对应数组里的函数依次执行

this.onResolvedCallbacks.forEach(fn=>fn());

this.onRejectedCallbacks.forEach(fn=>fn());

复制代码

我们都知道 Promise 有一个最为重要的 then 方法。为了保证链式调用, then 方法调用后返回一个新的 Promise,会将这个 Promise 的值传递给下一次 then 中。并且上一次 then 中不管是成功还是失败或是返回一个普通值,都会传递到下一次 then 的参数。

3、实现链式 then

首先我们知道,then 是有返回值的。而且可以一直 then 下去,所以之前的 then 必须返回一个新的 Promise。所以我们根据Promises/A+对 then 方法改造如下。

then(onFufilled,onRejected){
    let promise2;
    if(this.status==='resolved'){
        promise2=new Promise((resolve,reject)=>{
            let x=onFufilled(this.value);
            //判断p是不是一个promise,如果是取它的结果作为promise2成功的结果,
            //如果返回一个普通值,同样作为promise2成功的结果
            resolvePromise(promise2,x,resolve,reject);//解析p和promise2之间的关系
        });
    }
    if(this.status==='rejected'){
        promise2=new Promise((resolve,reject)=>{
            let x=onRejected(this.reason);
            resolvePromise(promise2,x,resolve,reject);
        })
    }
    if(this.status==='pending'){//当前既没有成功,也没有失败
        promise2=new Promise((resolve,reject)=>{
            this.onResolvedCallbacks.push(()=>{//存放成功的回调
                let x=onFufilled(this.value);
                resolvePromise(promise2,x,resolve,reject);
            }               
            );
            this.onRejectedCallbacks.push(()=>{//存放失败的回调
                let x=onRejected(this.reason);
                resolvePromise(promise2,x,resolve,reject);
            })
        })
    }
    return promise2;//调用then后返回一个新的promise
}
复制代码

resolvePromise 是干啥的呢?由于 then 可能返回任意值,所以根据Promises/A+规范对 then 返回的值进行如下处理或解析。

function resolvePromise(promise2,x,resolve,reject){
    //判断x是不是promise
    //如果当前返回的promise和x引用同一个对象报类型错误(不能自己等待自己完成)
    if(promise2===x){
        return reject(new TypeError('循环引用'));
    }
    //x不是null并且是对象或函数时,可能是promise
    if(x!==null&&(typeof x==='object'|| typeof x==='function')){
        let called; //标识当前promise有没有调用过
        try{//尽量让别人瞎写,防止取then时出现异常
            let then=x.then;//取x的then看是不是函数
            if(typeof then==='function'){//如果是函数就认为它是promise
                then.call(x,(y)=>{//第一个参数是this,后面的是成功的回调和失败的回调
                    if(called) return;
                    called=true;
                    resolvePromise(promise2,y,resolve,reject);//如果y是promise继续递归解析
                },(err)=>{//只要有一个失败了就失败了
                    if(called) return;
                    called=true;
                    reject(err);
                })
            }else{//then是一个普通对象直接成功
               resolve(x);
            }
        }catch(e){
            if(called) return;
            called=true;
            reject(e);
        }      
    }else{//如果x是普通值直接成功
        resolve(x);
    }
}
复制代码

值的穿透

我们用 Promise 时发现,当不给 then 中传入参数时,后面的 then 依旧可以得到之前 then 的返回值。例如:p.then().then(),这就是值的穿透。

then(onFufilled,onRejected){
    //解决onFufilled或onRejected没有传的问题
    onFufilled=typeof onFufilled==='function'?onFufilled:d=>d;
    onRejected=typeof onRejected==='function'?onRejected:e=>{throw e};
    let promise2;
    if(this.status==='resolved'){
        promise2=new Promise((resolve,reject)=>{
            let x=onFufilled(this.value);
            //判断p是不是一个promise,如果是取它的结果作为promise2成功的结果,
            //如果返回一个普通值,同样作为promise2成功的结果
            resolvePromise(promise2,x,resolve,reject);//解析p和promise2之间的关系
        });
    }
    if(this.status==='rejected'){
        promise2=new Promise((resolve,reject)=>{
            let x=onRejected(this.reason);
            resolvePromise(promise2,x,resolve,reject);
        })
    }
    if(this.status==='pending'){//当前既没有成功,也没有失败
        promise2=new Promise((resolve,reject)=>{
            this.onResolvedCallbacks.push(()=>{//存放成功的回调
                let x=onFufilled(this.value);
                resolvePromise(promise2,x,resolve,reject);
            }               
            );
            this.onRejectedCallbacks.push(()=>{//存放失败的回调
                try{
                    let x=onRejected(this.reason);
                    resolvePromise(promise2,x,resolve,reject);
                }catch(e){
                    reject(e);
                }
            })
        })
    }
    return promise2;//调用then后返回一个新的promise
}
复制代码

executor 执行的时候我们在外面包了 try{}catech 但是我们内部代码是异步的,就无法捕获错误了,需要给每个 then 中的方法都加一个 try{}catch

then(onFufilled,onRejected){
    //解决onFufilled或onRejected没有传的问题
    onFufilled=typeof onFufilled==='function'?onFufilled:d=>d;
    onRejected=typeof onRejected==='function'?onRejected:e=>{throw e};
    let promise2;
    if(this.status==='resolved'){
        promise2=new Promise((resolve,reject)=>{
            setTimeout(() => {
                try{
                    let x=onFufilled(this.value);
                    //判断p是不是一个promise,如果是取它的结果作为promise2成功的结果,
                    //如果返回一个普通值,同样作为promise2成功的结果
                    resolvePromise(promise2,x,resolve,reject);//解析p和promise2之间的关系  
                }catch(e){
                    reject(e);
                }           
            }, 0);

        });
        // return promise2;
    }
    if(this.status==='rejected'){
        promise2=new Promise((resolve,reject)=>{
            setTimeout(() => {
                try{
                    let x=onRejected(this.reason);
                    resolvePromise(promise2,x,resolve,reject);  
                }catch(e){
                    reject(e);
                }                 
            }, 0);
        })
        // return promise2;
    }
    if(this.status==='pending'){//当前既没有成功,也没有失败
        promise2=new Promise((resolve,reject)=>{
            this.onResolvedCallbacks.push(()=>{//存放成功的回调
                setTimeout(() => {
                    try{
                        let x=onFufilled(this.value);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        reject(e);
                    }                        
                }, 0);
            });
            this.onRejectedCallbacks.push(()=>{//存放失败的回调
                setTimeout(() => {
                    try{
                        let x=onRejected(this.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        reject(e);
                    }                     
                }, 0);
            })
        })
        // return promise2;
    }
    return promise2;//调用then后返回一个新的promise
}
复制代码

4、Promise 其他方法实现

4.1 catch

catch 接收的参数只有错误,也就相当于 then 方法没有成功的简写。而且 catch 后依然可以 then,那就简单暴力上代码吧!

catch(onRejected){
    return this.then(null,onRejected);
}
复制代码

4.2 resolve与reject

Promise.resolve()、Promise.reject() 这两种用法,是直接可以通过类调用的,原理就是返回一个内部是resolve 或 reject 的 Promise 对象。

Promise.resolve=function(val){
    return new Promise((resolve,reject)=>{
        resolve(val)
    })
}
Promise.reject=function(val){
    return new Promise((resolve,reject)=>{
        reject(val)
    })
}
复制代码

4.3 all

all方法的作用就是将一个数组的 Promise 对象放在其中,当全部 resolve 的时候就会执行 then 方法,当有一个 reject 的时候就会执行 catch,并且他们的结果也是按着数组中的顺序来的.

Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    }
  }
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject)
    }
  })
}
复制代码

5、测试

写了这么多,到底符不符合Promises/A+规范呢?

//promise的语法糖
Promise.defer=Promise.deferred=function(){
    let dfd={};
    dfd.promise=new Promise((resolve,reject)=>{
        dfd.resolve=resolve;
        dfd.reject=reject;
    })
    return dfd;
}
复制代码

安装promises-aplus-tests用来测试,安装:npm install promises-aplus-tests -g,测试:promises-aplus-tests + "文件名"。

到这基本就简单实现了一个自己的 Promise,此时对 Promise 的内部机制与它的编程思想有没有更深入的理解呢?新手可能一脸懵逼,大牛可能一脸蔑视。希望大家都有收获。写的不好,有问题欢迎大家在评论区评论指正(还不快去点赞⊙﹏⊙)!!!

代码地址

转载于:https://juejin.im/post/5b3cc0dd51882519ec07d803

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值