博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Promise 源码解析
阅读量:7053 次
发布时间:2019-06-28

本文共 9844 字,大约阅读时间需要 32 分钟。

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

promise的状态是什么时候变化的 ?

1:通过new Promise()生成的promise的状态变化过程:

当调用resolve时:

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

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

情况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的状态变化过程

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

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

情况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))

情况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

代码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

总结

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

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

你可能感兴趣的文章
Software: MPEG-7 Feature Extraction Library
查看>>
实习日记7.21
查看>>
Ural 1018 binary apple tree(显性树的树dp)
查看>>
[Linux学习]脚本文件名搜索
查看>>
操作ajax生成页面的一个问题
查看>>
Android开发之自定义Dialog简单实现
查看>>
hdu1528 Card Game Cheater
查看>>
dispatch_group_t
查看>>
菜鸟机器学习散点总结(三)
查看>>
Cocos Creator Animation 组件
查看>>
RH033读书笔记(1)-Lab2 Linux Usage Basics
查看>>
window对象 (浏览器对象模型)
查看>>
Loadrunner 关于参数赋值取值的操作
查看>>
C# 实现保留两位小数的方法
查看>>
Http协议4个新的http状态码:428、429、431、511;
查看>>
C#类型简述
查看>>
Go:字符串操作
查看>>
EXCEL 2010学习笔记 —— VLOOKUP函数 嵌套 MATCH 函数
查看>>
android graphics: 2D animation
查看>>
升级 python 2.6.6 系统到 2.7.10 版本
查看>>