架构思维实现promise,大爷,来瞅瞅

1,493 阅读11分钟

小葵花课堂开课了惊不惊喜,期不期待,今天扯点promise的事情,主要为了学习,最重要的是按照正常人的逻辑器理解认识,promise的架构,不是死记硬背怎么去实现它

首先提出两个问题

为什么要手写Promise?

Promise解决了什么问题?

  1. 回答,因为面试会问,哈哈...,那么面试为什么会问...,其实,学习编程不是学习配置一堆乱遭的东西,然后去写编程套路,写程序最重要的是你能心中有自己的一套架构思路,那么好的架构简单优雅,要设计好的架构就要从阅读架构开始,有本儿“贼拉儿”著名的书里说过:“编程语言是程序员表达的方式,而架构是程序员对世界的认知”,所以阅读和重写Promise这么经典的东西,学习他的思想和编程方式是非常有必要的
  2. Promise解决了什么,最最核心的问题就是回调的问题,在js的异步操作中,是通过回调来解决异步困扰的,回调多了就成了回调地狱

举个例子:

doOne()代表第一件事情,现在,我们想要在这件事情完成后,再做下一件事情doTwo(),应该怎么做呢? 先看看我们常见的回调模式。doOne()说:“你要这么做的话,就把doTwo()交给我,我在结束后帮你调用。”所以会是:

doOne(doTwo)

Promise模式又是如何呢?你对doOne()说:“不行,控制权要在我这里。你应该改变一下,你先返回一个特别的东西给我,然后我来用这个东西安排下一件事。”这个特别的东西就是Promise,这会变成这样

doOne().then(doTwo)

可以看出,Promise将回调模式从主从关系调换了一个位置,多个事件流程关系就可以集中到主干道上,而不是分散在各个事件函数之内。 那么如何做这样的转换呢?从最简单的情况来,假定doOne()代码是:

function doOne(callback){
    var value =1 ;
    callback(value);
}

那么他可以改变成

function doOne(){
    return {
        then :function(callback){
            var value = 1 ;
            callback(value);
        }
    }
}

这就完成了转换。虽然并不是实际有用的转换,但到这里,其实已经触及了Promise最为重要的实现要点,即Promise将返回值转换为带then方法的对象

无视你犀利的眼神继续我的表演:

好那我们继续按照这个思路往下来,假设第一件事情是异步的,那么在他执行过后,一定会有两种状态,要么成功,要么失败,所以这个成功后要做什么,失败后要做什么,这个是动态的是由用户决定的,所以then里最好传两个函数onFulfilled,onRejected,这两个函数都是由用户来定义的,将成果和失败的结果最参数传递给两个函数由用户来进行下一步操作,改写成这个样子

function doOne(){
    return {
        then:function(onFulfilled,onRejected){
        let value = 0;
        setTImeout(function(){
            value =1;
        },1000)
            //这个时候问题来了,我们怎么知道这个函数到底应该是走onFulfiled还是onRejected
            //同时还要给他传递响应的值?onFlfiled(value)还是onRejected(err)
        }
    }
}

那么这个时候,把新遇到的问题汇总一下。

  1. 我们不可能每次在执行方法的时候都在函数里写一大堆什么return,then之类的方法,我们需要一套通用的解决逻辑?
  2. 我们要通过一个状态来判定到底是走onFulfilled还是onRejected方法?
  3. 我们要解决的是异步问题,那么在调用这套通用逻辑时需要在等待代码执行时,做什么?是把onFulfile和onRejected存起来么?

好的我们来一个一个解决问题,第一个问题一套通用的逻辑,Promse的办法在外面包上一层函数,形成个闭包,那么我们就可以在闭包中写一些固定的逻辑了

function promise (executor){
    excutor();
    //excutor是用户定义的,里面可以执行doOne
}

这时候在改写下doOne和doTwo的例子

function promise (executor){
    excutor();
    return {
        then:function (onFlfiled,onRejected){
        ...
        }
    }
}
promise (doOne);

可以在excutor立即执行函数外面加个try,catch去判定当前的执行状态,在catch中去执行then的onRejected,代码如下:

function promise (executor){
    try{
        excutor();
    } catch(e){
        onRejected(e);//我们要的是这个意思,但是这个时候调用onRejected是调用不到的,所以必须通过一个什么东西来存取当前的状态,在调用then的时候在走响应的函数,类似于发布订阅模式
    }
    return {
        then:function (onFlfiled,onRejected){
            
        }
    }
}

所以then里要间接的通过一个状态(发布订阅模式的事件)来判断,是走onFulfiled还是onRejected,那么这个函数既要维护状态,还要返回对象,同时对象上还要带有方法的属性,我们可以吧promise改写成构造函数的模式

class Promise{
    construct(excutor){
        this.state= pending;//异步执行阶段等待状态
        try{
        excutor();
        }catch(e){
        this.state=rejected;
        }
    }
 
    then(onFulfiled,onRejected){
    if(this.state === rejected){
        onEjected();
    }
    ...
    //这个时候又他么写不下去了,onReject应该传递个值为e的参数,那么这个值应该保存起来,同时合适调用onFulfiled是个问题
    //所以我们应该给excutor传个参数,这个参数也是个方法,当在excutor里面执行完操作后运行这个方法,将状态置为成功态
    //同时要确保状态只能从等待太变成成功态或者失败态,切不可逆
        
    }
}

如代码中所言又遇到了问题

此时,问题汇总下:

  1. then中执行函数,要接受到e的值,所以这个值要存起来
  2. 所以我们应该给excutor传个参数,这个参数也是个方法,当在excutor里面执行完操作后运行这个方法,将状态置为成功态
  3. 同时要确保状态只能从等待太变成成功态或者失败态,切不可逆

代码如下:

class Promise {
    construct(){
        this.state = "pending";
        this.resolveAry=[];
        this.rejectAry=[];
        try{
            excutor(resolve,reject);//加完resolve,应该想到那用户也应该也能自己把状态制成失败态,索性在加个reject
        }catch(e){
            this.state=rejected;
        }
    }
    resolve(data){
        if(this.state === "pending"){//状态不可逆,通过状态来判定执行
            this.state ="resolve"
            this.value = data;
            this.resolveAry.forEach((item,index)=>{
                item(this.value);
            })
        }
    }
    reject(data){
        if(this.state ==="pending"){
            this.state ="rejected";
            this.value = data;
            this.rejectAry.forEach((item,index)=>{
                item(this.value);
            })
        }
    }
    then(onFulfiled,onRejected){
        if(this.state === 'rejected'){
            onRejected();
        }else if (this.state === 'resolve'){
            onFulfiled();
        }else if (this.state === 'pending'){//  如果在等待态是,将函数都分别存起来,状态改变后,在去执行
            this.resolveAry.push(onFulfiled);
            thi.rejectAry.push(onRejected);
        }
    }
}

将上面的代码拿去编辑器中调试一下,发现因为roslve,reject虽然在构造函数中定义,但是执行时是用户同过excutor来执行的,所以this不是原先的this,所以我需要原来的this,这时候当你需要某个作用域的值,最好的方式就是创造个闭包,把this存起来,通过作用域链来找到它,代码如下:

class Promise {

    constructor(excutor){
        this.state = "pending";
        this.resolveAry=[];
        this.rejectAry=[];
        this.value =null;
        this.self = this;
        try{
            let resolve = this.resolve();
            let reject = this.reject();
            excutor(resolve,reject);
        }catch(e){
            this.state="rejected";
            this.value = e;
            this.rejectAry.forEach((item,index)=>{
                item(e);
            })
        }
    }
    resolve (){
        let self = this;
        return (data)=>{
            if(self.state === "pending"){
                self.state ="resolve"
                self.value = data;
                self.resolveAry.forEach((item,index)=>{
                    item(self.value);
                })
            }
        }
    }
    reject(){
        let self = this;
        return (data)=>{
            if(self.state ==="pending"){
                self.state ="rejected";
                self.value = data;
                self.rejectAry.forEach((item,index)=>{
                    item(self.value);
                })
            }
        }

    }
    then(onFulfiled,onRejected){
        if(this.state === 'rejected'){
            onRejected(this.value);
        }else if (this.state === 'resolve'){
            onFulfiled(this.value);
        }else if (this.state === 'pending'){
            this.resolveAry.push(onFulfiled);
            thi.rejectAry.push(onRejected);
        }
    }
}

代码写到这,已经非常像原生的promise了,突然想起有个很重要的基础问题还没解决

妈的

要想实现流程控制,最基础的就是要做到能够链式调用,也就是说then完了,我还特么能then,你就说皮不皮,一说到链式调用,我们立马就会想到this,然而我们稍微在仔细想一想就会发现我们既然是流程控制,每一步做的都不是同一件事或者说不是同一个方法或对象,既然一段方法已经生成了一个状态,就不可能在有另一个状态,那么要实现链式,就要返回一个新的promise,它有一个新的then让你去链式一下。

好,那实现起来就简单多了,上代码:(前面滤过直接看then里的变化)

class Promise {
    constructor(excutor){
        this.state = "pending";
        this.resolveAry=[];
        this.rejectAry=[];
        this.value =null;
        this.self = this;
        try{
            let resolve = this.resolve();
            let reject = this.reject();
            excutor(resolve,reject);
        }catch(e){
            this.state="rejected";
            this.value = e;
            this.rejectAry.forEach((item,index)=>{
                item(e);
            })
        }
    }
    resolve (){
        let self = this;
        return (data)=>{
            if(self.state === "pending"){
                self.state ="resolve"
                self.value = data;
                self.resolveAry.forEach((item,index)=>{
                    item(self.value);
                })
            }
        }
    }
    reject(){
        let self = this;
        return (data)=>{
            if(self.state ==="pending"){
                self.state ="rejected";
                self.value = data;
                self.rejectAry.forEach((item,index)=>{
                    item(self.value);
                })
            }
        }

    }
    then(onFulfiled,onRejected){
        let self = this;
        let promise2= new Promise((resolve,reject)=>{//从新包装一层promise,应为promise里是立即执行函数,所以不会有任何影响,还加了层promise
            if(self.state === 'rejected'){
               let x = onRejected(self.value);
               reject(x);
            }else if (self.state === 'resolve'){
                let x = onFulfiled(self.value);
                resolve(x);
            }else if (self.state === 'pending'){//pending状态时应为要存起来,但是我们还需要当前函数的resolve,包层函数
                self.resolveAry.push((data)=>{
                    let x = onFulfiled(data);
                    resolve(x);
                });
                self.rejectAry.push(()=>{
                    let x = onRejected(data);
                    resolve(x);
                });
            }
        })
        return promise2;
    }
}

写到这代码已经实现了,链式调用,也就是说promie.then()的返回值可以继续then下去,实现我们想要的流程控制,但是现在问题又来了:

如果在then中去接着去调用priomise或者引用其他人的promise怎么办?

是时候要祭出神器了

在promiseA+中为了处理这个问题,规定了一个resolvePromise函数,照着PromiseA+来写出了下面逻辑

resolvePromise=(promise2,x,resolve,reject)=>{
    if(promise2 ===x){
        return reject(new TypeError('循环引用'))
    }
    if(x!=null&&(typeof x ==='object'||typeof x ==='function')){
        try{// 根据PromiseA+规范[promiseA+2.3.3.2]
            let then = x.then
            if(typeof then ==='function'){//then是和个函数时暂且认为他是promies[promiseA+2.3.3.3]
                then.call(x,(y)=>{
                    resolve(y)
                },(e)=>{
                    reject(e);
                })
            } else {
                resolve(x);
            }
        }catch(e){
            reject(e);
        }
    }else {//x就是一个普通值,返回promise的成功
        resolve(x)
    }
}

promiseA+2.3.3.2 promiseA+2.3.3.3

增加递归调用,解决多个promise嵌套问题

resolvePromise=(promise2,x,resolve,reject)=>{
    if(promise2 ===x){
        return reject(new TypeError('循环引用'))
    }
      let called; // 用来防止其他人写的promise即调用resolve,有调用reject多次调用
    if(x!=null&&(typeof x ==='object'||typeof x ==='function')){
        try{// 根据PromiseA+规范[promiseA+2.3.3.2]
            let then = x.then
            if(typeof then ==='function'){//then是和个函数时暂且认为他是promies[promiseA+2.3.3.3]
                then.call(x,(y)=>{
                    if (called) return;
                    called = true;
                    //resolve(y)
                    resolvePromise(promise2,y,resolve,reject);//递归调用,防止多个promise嵌套
                },(e)=>{
                    if (called) return;
                    called = true;
                    reject(e);
                })
            } else {
                resolve(x);
            }
        }catch(e){
            if (called) return;
            called = true;
            reject(e);
        }
    }else {//x就是一个普通值,返回promise的成功
        resolve(x)
    }
}

这时候代码,已经基本与promise一致了,我们发现原生promise其实在then的时候,可以不写任何东西,会直接穿透,所以在then中加入下面逻辑:

    then(onFulfiled,onRejected){
         onfulfilled = typeof onfulfilled == 'function' ? onfulfilled :  val=>val;
         onrejected = typeof onrejected === 'function' ? onrejected :err => {
             throw err;
         }
        let self = this;
        let promise2= new Promise((resolve,reject)=>{
            if(self.state === 'rejected'){
               let x = onRejected(self.value);
               resolvePromise(promise2, x, resolve, reject)
            }else if (self.state === 'resolve'){
                let x = onFulfiled(self.value);
                resolvePromise(promise2, x, resolve, reject)
            }else if (self.state === 'pending'){
                self.resolveAry.push((data)=>{
                    let x = onFulfiled(data);
                    resolvePromise(promise2, x, resolve, reject)
                });
                self.rejectAry.push(()=>{
                    let x = onRejected(data);
                    resolvePromise(promise2, x, resolve, reject)
                });
            }
        })
        return promise2;
    }

现在还有最后一个小问题,就是在PromiseA+中明确规定promise A+ 2.2.4,then中是异步的,那么在原生中用“微任务”来实现的,也就是说在执行完同步代码后首先执行的,那么我们可以用setTimeout(宏任务)去模拟一下,把它放在宏任务队列中,在效果上区别不太大,如果与原生混着用,那可能在执行顺序上有点小瑕疵,不过我也没测试过,有兴趣的可以看一看,最后改写完的代码是这个样子的:

resolvePromise=(promise2,x,resolve,reject)=>{
    if(promise2 ===x){
        return reject(new TypeError('循环引用'))
    }
      let called; // 用来防止其他人写的promise即调用resolve,有调用reject多次调用
    if(x!=null&&(typeof x ==='object'||typeof x ==='function')){
        try{// 根据PromiseA+规范[promiseA+2.3.3.2]
            let then = x.then
            if(typeof then ==='function'){//then是和个函数时暂且认为他是promies[promiseA+2.3.3.3]
                then.call(x,(y)=>{
                    if (called) return;
                    called = true;
                    //resolve(y)
                    resolvePromise(promise2,y,resolve,reject);//递归调用,防止多个promise嵌套
                },(e)=>{
                    if (called) return;
                    called = true;
                    reject(e);
                })
            } else {
                resolve(x);
            }
        }catch(e){
            if (called) return;
            called = true;
            reject(e);
        }
    }else {//x就是一个普通值,返回promise的成功
        resolve(x)
    }
}

export class Promise {
    constructor(excutor){
        this.state = "pending";
        this.resolveAry=[];
        this.rejectAry=[];
        this.value =null;
        this.self = this;
        try{
            let resolve = this.resolve();
            let reject = this.reject();
            excutor(resolve,reject);
        }catch(e){
            this.state="rejected";
            this.value = e;
            this.rejectAry.forEach((item,index)=>{
                item(e);
            })
        }
    }
    resolve (){
        let self = this;
        return (data)=>{
            if(self.state === "pending"){
                self.state ="resolve"
                self.value = data;
                self.resolveAry.forEach((item,index)=>{
                    item(self.value);
                })
            }
        }
    }
    reject(){
        let self = this;
        return (data)=>{
            if(self.state ==="pending"){
                self.state ="rejected";
                self.value = data;
                self.rejectAry.forEach((item,index)=>{
                    item(self.value);
                })
            }
        }
    }
    then(onFulfiled,onRejected){
         onfulfilled = typeof onfulfilled == 'function' ? onfulfilled :  val=>val;
         onrejected = typeof onrejected === 'function' ? onrejected :err => {
             throw err;
         }
        let self = this;
        let promise2= new Promise((resolve,reject)=>{
            if(self.state === 'rejected'){
                setTimeout(()=>{
                    try{
                       let x = onRejected(self.value);
                       resolvePromise(promise2, x, resolve, reject)
                    }catch(e){
                        reject(e);
                    }
                },0)
            }else if (self.state === 'resolve'){
                setTimeout(()=>{
                      try{
                        let x = onFulfiled(self.value);
                        resolvePromise(promise2, x, resolve, reject)  
                    }catch(e){
                        reject(e);
                    }
                },0)
            }else if (self.state === 'pending'){
                self.resolveAry.push((data)=>{
                    setTimeout(()=>{
                        try{
                            let x = onFulfiled(data);
                            resolvePromise(promise2, x, resolve, reject)
                        }catch(e){
                            reject(e);
                        }
                    },0)
                });
                self.rejectAry.push(()=>{
                    setTimeout(()=>{
                        try{
                            let x = onRejected(data);
                            resolvePromise(promise2, x, resolve, reject)
                        }catch(e){
                            reject(e);
                        }
                    },0)
                });
            }
        })
        return promise2;
    }
}

大工告成,夜已深了,我心疲惫,就不校验了,有哪写错的地方请见谅,最后配个图乐呵乐呵

稳住,能赢