寫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的狀態,並執行相關的回調函數。