Promise 源码解析

写promise的时候,发现本身应用了树,链表,前度遍历等方法,以为对于部分想了解promise机制,而且加深数据结构学习的同窗有些许帮助,决定写一篇文章分享出来。不过文笔实在堪忧,没有耐心看下去的同窗,能够直接看源码。源码地址 .promise

promise的状态是何时变化的 ?

1:经过new Promise()生成的promise的状态变化过程:
当调用resolve时:
数据结构

状况1:
    promise1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('promise1');
        }, 1000)
    })
复制代码

状况1: 当参数是一个非promise的时候,1秒后promise的状态当即变成resolve,并执行then里面的事件.
app

状况2: 
    promise1 = new Promise((resolve) => {
        setTimeout(() => {
            promise2 = new Promise((resolve, reject) => {
                resolve('promise2');
            })
            resolve(promise2);
        }, 1000)
    })
复制代码

状况2: 当参数是另外一个promise的时候,这时promise1的状态由promise2来决定,何时promise2变化了状态,promise1的状态也会相应的变化,而且状态保持一致.
异步

当调用reject时:
函数

这里与resolve不一样的是,reject无论参数是什么,状态都会当即变为reject。
学习

2:经过then()或者catch()生成的promise的状态变化过程
this

状况1:
    promise1 = new Promise((resolve) => {
        resolve('promise1');
    })
    promise2 = promise1.then((data) => {
        return 'promise2';
    })
复制代码

状况1: 当回调函数里面直接return一个非promise,和上面的状况1同样,当前的promise2状态变为resolve。至关于执行了(resolve('非promise'))
spa

状况2:
    promise1 = new Promise((resolve) => {
        resolve('promise1');
    })
    promise2 = promise1.then((data) => {
        promise3 = new Promise((resolve, reject) => {
            resolve('promise3');
        })
        return promise3;
    })
复制代码

状况2: 当回调函数里面直接return一个promise3,和上面状况2同样,当前promise2的状态依赖于primise3,至关于执行了(resolve(promise3))
prototype

状况3:
    promise1 = new Promise((resolve) => {
        resolve('promise1');
    })
    promise2 = promise1.then((data) => {
        console.log( iamnotundefined );
    })
复制代码

状况3: 当回调函数里面代码报错了,而且没有被catch到的,当前promise状态变为reject.(异步的error代码catch不到,不会影响promise状 态变化)
设计

经过几个promise例子说明一下

promise1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('promise1_resolve_data');
    },1000)
})
console.log(promise1);
promise2 = promise1.then((data) => {
    console.log('promise2---', data);
    return 'promise2';
})
console.log(promise2);
promise3 = promise1.then((data) => {
    console.log('promise3---', data);
    return 'promise3';
})
console.log(promise3);
setTimeout(() => {
    console.log('--promise1--', promise1);
    console.log('--promise2--', promise2);
    console.log('--promise3--', promise3);
},3000)
复制代码

代码执行结果:

依次输出promise1,promise2,promise3,状态都是pendding.一秒事后执行relove,promise1状态变为resolve,值为'promise1_resolve_data'.以后依次执行promise1.then里面的回调函数,promise2状态变为resolve,值为'promise2'.promise3状态变为resolve,值为'promise3'.

上面代码段看出了什么?

1:当初始化promise1,promise2,promise3后,三个promise的状态都是pendding.
2:当promise1里面的resolve执行后,promise1的状态当即变为resolve,值为resolve函数参数.
3:promise2,promise3都是经过promise1的then方法生成出来的,而且在promose1状态变为resolve以后也都依次状态变为了resolve。

经过上面的代码能够先得出的结论是:

(1) : 每个promise的状态的变化都不是当即就变化得,而是在将来的某一个时刻变化的。这里能够想到:当咱们本身实现的时候,必定要有一个结构去维护着全部promise.

(2) : 什么结构呢? 这里能够看出,promise2,promise3都是由promise1的then方法返回的,能够看出这是一个一对多的关系结构,因此这里的结构必定是一个树的结构。

(3) : 何时去'装载'每个promise和相关的事件呢?很简单,then和catch方法里面。

(4) : 何时去'执行'promise状态变化,相关的事件回调? resolve,reject里面。

(5) : 说白了,也就是两个过程,装载过程(then,catch),执行过程(resolve,reject)

开始写代码

当去实现一个东东的时候,好比promise,首先要作的是熟悉promise的语法,特性。分析每个promise之间的关系,而后才能肯定一个合适的数据结构去存储它。前期的结构关系设计合理了,代码写起来也会很容易。

只写核心代码
    function PP(){
        let promises = new Map(); // 存储全部的promise实例
        let index = 1;
        // Promise 构造函数
        function App(fn){
            this.state = "pendding";
            this.id = +index; //每一个promise的惟一标识
            fn(this.resolve.bind(this), this.reject.bind(this));
        }
        return App;
    }
    代码很简单,不作解释
复制代码

前面说到了,promise的实现其实就是两个过程,装载执行. 先说下装载过程,也就是then() catch()的实现

只写核心代码
    App.prototype.then = function(resolve, reject){
        let instance = new App(()=>{}); // 生成一个初始状态的promise,并返回
        //把instance和相应的回调保存起来 
        /**
         * type: 用来断定这个promise是经过then方法建立的
         * instance: promise实例
         * callback: 保存的事件 
         */
        let item = {
            type : 'then', 
            instance : instance,
            callback : length > 1 ? ([{
                status : 'resolve',
                fn : resolveFn
            },{
                status : 'reject',
                fn : rejectFn
            }]) : ([{
                status : 'resolve',
                fn : resolveFn
            }])
        }
        // 这里经过map存储的,两个promise之间的关系就经过promise的_id相互关联.
        let p_item;
        if(p_item = promises.get(this._id)){
            p_item.push(item);
        }else{
            promises.set(this._id,[item])
        }
        return instance;
    }
    
    App.prototype.catch = function(rejectFn){
        // 和then差很少
        let instance = new app(()=>{});
        let item = {
            type : 'catch',
            instance : instance,
            callback : ([{
                status : 'reject',
                fn : rejectFn
            }])
        }
        let p_item;
        if(p_item=promises.get(this._id)){
            p_item.push(item);
        }else{
            promises.set(this._id,[item])
        }
        return instance;
    }
复制代码

说下执行的过程 , resolve() , reject()的实现 。
辅助案例: 代码 2-1

promise1 = new Promise((resolve, reject) => {
        setTimeout(()=>{
            resolve("resolve data from promise1");
        },1000)
    })
    promise2 = promise1.then((data) => {
        console.log('promise2---', data);
        return 'promise2';
    })
    promise3 = promise2.then((data) => {
        console.log('promise3---', data);
        return 'promise3';
    })
    promise4 = promise1.then((data) => {
        console.log('promise4---', data);
        return 'promise2';
    })
    promise5 = promise1.catch((data) => {
        console.log('promise4---', data);
        return 'promise2';
    })
复制代码

这是代码2-1,promise之间的关系图

执行过程:一秒后执行了resolve方法,当前promise状态变为resolve.以后拿出与promise1下面的三个promise,分别是promise2,promise4,promise5.以后拿出每个promise相关的事件,并执行。上面说了,像promise2,promise4,promise5这些经过then或者catch生成的promise,状态变化过程由返回值来决定。

App.prototype.resolve = function(data){
        let ans = null; // 回调函数的结果
        let promise = null; //每个子节点中的promise实例
        let items; //一个节点下面的子节点
        //执行mic任务队列里面的任务 , 这里用setTimeout(fn,0)代替
        setTimeout(() => {
            // 上面说到,这里作的事就是处理promise的变化。
            if(typeof data == 'object' && data!==null &&  data.__proto__.constructor == app){
                // 若是传入的参数是一个promise对象,这个时候当前的promise的状态不是当即变化的,而是依赖于传入的promise也就是data的变化而变化。
                // 因此这里要作的是就是关联这两个promise,这里我用的链表
                data.previous = this;
            }else{
              // 这里也就是上面说的状况1,resolve传入的参数是一个非promise,这个时候当前promise当即变化,并执行相关的事件回调.
              setTimeout(() => {
                this.state = "resolve";
                this.value = data;
                loadLineToList(this); // (很重要,单独解释2)
                //拿出当前节点下面的全部子节点
                if(items = promise.get(this._id)){
                    // 这里以2-1示例代码为例,分别拿出promise2,promise4,promise5 .
                    // 上面promise项里面的数据结构,分别是 type字段,instance字段,callback字段。在then,或者catch里面有写😊
                    // 拿出每个promise的callback,并执行
                    for(let i=0;i<items.length;i++){
                        if(items[i].type == 'then'){
                            try{
                                ans = items[i].callback[0].fn(data);
                            }catch(err){
                                promise = promises.get(this._id)[i].instance;
                                promise.reject(err);
                                continue;
                            }  
                        }
                        //这里已经拿到了事件执行的结果,ans 
                        if(typeof ans == 'object' && ans!==null &&  ans.__proto__.constructor == app){
                            ans.previous = promise;
                        }else{
                            if(promise){
                                promise.resolve(ans);
                            }
                        }
                    }
                }else{
                    //下面没有节点了,出口
                    return;
                }
              },0)
            }
        },0)
    }
复制代码

代码2-2,返回值都是非promise,处理过程如上。接下来讲另外一种状况,返回值是promise , loadLineToList()这个函数就是用来处理这种状况的

promise1 = new Promise((resolve, reject) => {
        setTimeout(()=>{
            promise2 = new Promise((resolve) => {
                setTimeout(() => {
                    promise5 = new Promise((resolve) => {
                        resolve('promise5');
                    })
                    promise7 = promise5.then(() => {
                        
                    })
                    resolve(promise5);
                },1000)
            })
            console.log('1s');
            resolve(promise2);
        },1000)
    })
    promise3 = promise1.then((data) => {
        console.log(data);
        promise4 = new Promise((resolve) => {
            setTimeout(() => {
                resolve('promise4');
            },1000)
        })
        return promise4;
    })
    promise6 = promise3.then((data) => {
        console.log(data);
    })
    setTimeout(() => {
        console.log('--promise1--', promise1);
        console.log('--promise2--', promise2);
        console.log('--promise3--', promise3);
        console.log('--promise4--', promise4);
        console.log('--promise5--', promise5);
        console.log('--promise6--', promise6);
    },4000)
复制代码

上面是代码2-2,promise关系图,分析出每个promise之间的关系是重要的,只有明确了关系才能设计出合适的数据结构。

上面的代码说明了参数是promise的状况 , promise1的变化依赖于promise2, promise2的状态依赖于promise5. 一样的,promise3的状态依赖于promise4. 这里能够清晰的看出,promise之间的关系是单向的,1对1的,因此用链表是合适的。

App.prototype.then代码中的data.previous = this;ans.previous = promise;用来创建链表的。loadLineToList这个函数用来处理链表中promise以前的关系。保持promise1,promise2,promise5状态一致,而且把promise2,promise5下面的全部promise'移'到promise1的下面。

reject的实现

说reject实现以前,先说明下promise的catch机制。

promise1 = new Promise((resolve, reject) => {
        reject('promise1');
    })
    promise2 = promise1.then(() => {
    
    });
    promise4 = promise1.then(() => {
    
    });
    promise3 = promise2.catch(() => {
    
    })
复制代码

上面代码会报一个 Uncaught Error: (in promise) promise1,若是没有最后的promise3的catch,会报2个Uncaught Error: (in promise) promise1

promise之间的关系是树形的,当一个节点状态变成了reject,那么必定要在此节点的下面一条线路上,有一个节点去catch这个reject,否则就会报错。像上面的promise1变成了reject,会向下面的子节点去'发散',promise2没有catch,那么promise2的状态变成reject,而且继续向下找,promise3catch到了,而后结束。另外一条线路,promise4没有catch到,状态变为reject,因为下面没有节点了,也就是没有catch,因此会抱一个Uncaught Error: (in promise) promise1

说清了catch机制,再去写reject相关的代码就容易了。

App.prototype.reject = function(error){
        let promise = null; //子节点
        let fn = null;  //then or catch的回调函数
        setTimeout(() => {
            this.state = "reject";
            this.value = error;
            loadLineToList(this);
            let list = promises.get(this._id);//拿出当前节点下面的全部子节点
            //出口,没有找到,报错
            if(!list || list.length==0){
                throw new Error("(in promise) "+error);
            }
            for(let i=0;i<list.length;i++){
                promise = list[i].instance; // 从左的第一个子节点开始
                type = list[i].type;
                if(type == 'then'){   // 这个promise 是经过p1.then() 出来的 , 可是因为p1是reject , 因此当前promise转换成reject
                    //处理then有两个回调函数的状况,第一个回调函数至关于catch
                    if(list[i].callback.length == 1){
                        promise.value = error;
                        promise.reject(error);
                        continue;
                    }else{
                        fn = list[i].callback[1].fn;
                    }
                }
                // 拿到catch里面的fn
                if(!fn){
                    fn = list[i].callback[0].fn; 
                }
                
                let ans = null; // 回调函数的结果
                // catch回调函数里的代码,若是代码报错,当前promise变为reject
                try{
                    ans = fn(error);
                    fn = null;
                }catch(err){
                    promise.reject(err);
                    continue;
                }
                promise.value = ans;
                if(typeof ans == 'object' && ans!==null &&  ans.__proto__.constructor == App){
                    ans.previous = promise;
                }else{
                    if(promise){
                        promise.resolve(ans);
                    }
                }
            }
        }, 5)
    }
复制代码

总结

其实看promise的实现,每个promise之间的关系是经过树的结构相互联系的。实现也是分为两个过程,装载和执行。装载也就是构建树的过程,catch和then方法。执行就是经过resolve和reject方法前度遍历去找出下面的节点,改变每个promise的状态,并执行相关的回调函数。

相关文章
相关标签/搜索