写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-1promise1 = 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)复制代码
上面的代码说明了参数是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的状态,并执行相关的回调函数。