写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状 态变化)
设计
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)
复制代码
上面的代码说明了参数是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实现以前,先说明下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的状态,并执行相关的回调函数。