Promise原始碼解析-步步為營皆可及

風雪中的兔子發表於2019-03-31

本文根據promise的用法, 帶您一步一步的瞭解promise的原始碼內幕; 本文采用要點、使用、原始碼分析的思路,步步為營,一點一點的剖析Promise的神祕世界; 本文Promise原始碼部分採用es5語法

Promise定義

  • promise是非同步解決方案; 字面意思是"承諾",即未來某個時刻才能得到結果;
  • 從語法上來說,它是一個物件,從它可以獲取非同步操作的訊息;

基本用法 - 建構函式

ES6 規定,Promise物件是一個建構函式,用來生成Promise例項

  • 使用:
	let p = new Promise();
複製程式碼
  • 原始碼
function Promise(){ //... }
複製程式碼
  • 解析: 建構函式與普通的函式並無二致, 只是我們在使用時需要通過new的方式來呼叫; 其中建構函式中的this指向new生成的物件例項;

Promise建構函式接收一個執行器函式(executor)作為引數, 該函式的兩個引數分別為resolve和reject。當new生成物件例項的時候,Promise的引數executor會立即執行

  • 使用:
	let p = new Promise((resolve, reject)=>{
		// coding....
	});
複製程式碼
  • 原始碼:
	function Promise(executor){
		
		executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	}
複製程式碼
  • 解析: executor即呼叫時候傳遞進來的函式 (resolve, reject)=>{ // coding... }, 呼叫時會傳遞兩個引數進去, 供// coding....處使用

執行器executor的引數(resolve, reject)是兩個函式, 這兩函式的作用:

  • resolve函式是將Promise物件的狀態從"pending"變為"resolved"('未完成' => '成功'), 在非同步操作成功時呼叫,並將非同步操作結果作為引數傳遞出去(原始碼的resolve函式中接收);
  • reject函式 是將Promise物件的狀態從"pending"變為"rejected"('未完成' => '失敗'), 在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去(原始碼的reject函式中接收);
  • 使用:
	let p = new Promise((resolve, reject)=>{
	    // coding....
	    resolve('成功');
	    // 或 reject('失敗');
	});	

複製程式碼
  • 原始碼:
	function Promise(executor){
	
	    function resolve(value){
	        console.log(value);   	// 呼叫resolve('成功'); 	=> 成功
	    }
	 
	    function reject(reason){
	        console.log(reason);		// 呼叫reject('失敗');	=> 失敗 
	    }
	
	    executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	}
複製程式碼
	// 升級(新增狀態)
	function Promise(executor){
		this.status = 'pending';		// 用於儲存promise例項的狀態,預設為pending,等待態
		this.value 	= undefined;		// 儲存成功時的值
		this.reason = undefined;		// 儲存失敗時的原因

	    function resolve(value){
	        console.log(value);   	// 呼叫resolve('成功'); 	=> 成功
			this.value = value;
	    }
	 
	    function reject(reason){
	        console.log(reason);		// 呼叫reject('失敗');	=> 失敗 
			this.reason = reason;
	    }
	
	    executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	}
複製程式碼
	// 升級(狀態的轉變) pending => fulfilled 或 pending => rejected
	//一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件新增回撥函式,也會立即得到這個結果[摘自:http://es6.ruanyifeng.com/#docs/promise]

	function Promise(executor){
		let self 	= this;
	    this.status = 'pending';  // pending => fulfilled | rejected
	    this.value  = undefined;
	    this.reason = undefined;
	    // 成功 
	    function resolve(value){
	        if(self.status === 'pending'){
	            self.value  = value;
	            self.status = 'fulfilled';
	            console.log(self.status)
	        }  
	    }
	
	    function reject(reason){
	        if(self.status === 'pending'){
	            self.reason = reason;
	            self.status = 'rejected';
	            console.log(self.reason)
	        }
	    }
	
		// 需要通過別名引用的方式; 因為resolve和reject中的this指向的是全域性物件
	    executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	}

複製程式碼
  • 解析:

    1. promise有三個狀態: pending(進行中或等待中)、fulfilled(已成功)、rejeced(已失敗)
    2. 使用時呼叫resolve函式,會把promise的狀態轉變為fulfilled,並且將成功的結果儲存到promise例項的value屬性
    3. 使用時呼叫reject函式,會把promise的狀態轉變為rejected,並且將失敗的原因儲存到promise例項的reason屬性
    4. 狀態的轉變只能從pending => fulfilled 或 rejected, fulfilled和rejected之間是不能轉換的;所以在resolve和rejected函式中新增了狀態判斷
    5. 呼叫resolve函式或reject函式會修改promise例項value(成功的結果)屬性或reason(失敗的原因),而此時在resolve和reject內部如何引用到promsie例項呢? -> 在Promise建構函式內部設定this的引用: let self = this; 在resolve或reject函式中,通過self引用promise例項;
  • 小結: 程式碼書寫到此刻,我們列印Promise例項, 即可檢視到當前例項的狀態和相關屬性;

let p = new Promise((resolve, reject)=>{
    resolve('成功');
	/*
	  if (/* 非同步操作成功 */){
	    resolve(value);
	  } else {
	    reject(error);
	  }
	*/
});
console.log(p);  // Promise { status: 'fulfilled', value: '成功', reason: undefined }

複製程式碼

如果Promise的執行器函式(executor)裡面直接丟擲異常呢? 捕獲錯誤,相對於執行了reject

  • 使用:
let p = new Promise((resolve, reject)=>{
	throw new Error('錯誤扔給你, 來打我呀^_^');
});
複製程式碼
  • 原始碼:
function Promise (executor){
    // 在promise內部定義一個狀態 當前promise的狀態
    let self = this;
    self.value = undefined;
    self.reason = undefined
    self.status = 'pending'; // 預設promise的狀態是pengding
    self.onResolevedCallbacks = []; // 存放所有成功的回撥
    self.onRejectedCallbacks = []; // 存放所有失敗的回撥
    function resolve(value){
        // (value!=null && typeof value === 'object') || typeof value == 'function'
        if(value instanceof Promise){
            console.log('here');
            // if(value.then && typeof value.then === 'function'){
                return value.then((data)=>{
                    console.log('11111111111')
                    console.log(data)
                    resolve(data)
                },y=>{
                    reject(y);
                });
            // }
        }
        console.log(self.status)
        if(self.status === 'pending'){
            self.value = value;
            console.log('pending:',value)
            self.status = 'resolved'; // 成功態
            console.log('pending--', self.onResolevedCallbacks)
            self.onResolevedCallbacks.forEach(fn=>fn());
        }
    }
    function reject(reason){
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected'; // 失敗態
            // 釋出
            self.onRejectedCallbacks.forEach(fn =>fn());
        }
    }

	// ---------------修改在這裡-------------------------------------------------------------------------
	// 小樣,敢給大爺拋錯誤, 直接捕獲了,讓你的錯誤銷聲匿跡 @<_>@
    try{
        executor(resolve,reject); // 使用者會呼叫resolve || reject
    }catch(e){
        reject(e); // 說明失敗了
    }
}
複製程式碼
  • 解析:

上面程式碼理解了,請移步下一個關 注意,為了行文方便,後面的resolved統一隻指fulfilled狀態,不包含rejected狀態


Promise.prototype.then()

Promise.prototype.then()方法是Promise的核心方法; Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式;then方法的作用就是為promise例項新增狀態改變時的回撥函式;

  • 使用:
let p = new Promise((resolve, reject)=>{
    resolve('成功');
    // reject('失敗');
});

p.then((value)=>{
    // 成功時執行的回撥
    console.log('執行resolve,輸出例項成功態的vlaue屬性: ', value);
}, (reason)=>{
    // 失敗時執行的回撥
    console.log('執行reject,輸出例項失敗態的reason屬性: ', reason);
});

// => 執行resolve,輸出例項成功態的vlaue屬性:  成功
複製程式碼
  • 原始碼:
function Promise(executor){
	
	let self = this;
    self.status = 'pending';  // pending => fulfilled | rejected
    self.value  = undefined;
    self.reason = undefined;
    // 成功 
    function resolve(value){
        if(self.status === 'pending'){
            self.value  = value;
            self.status = 'fulfilled';
        }  
    }

    function reject(reason){
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
        }
    }
	
    try {
        executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
    }catch(err){
        reject(err);
    }
}

// Promise的核心方法
Promise.prototype.then = function(onFulfilled, onRejected){
    if(this.status === 'fulfilled'){
        onFulfilled(this.value); // 這裡傳遞成功的值
    }

    if(this.status === 'rejected'){
        onRejected(this.reason);  // 這裡傳遞失敗的原因或錯誤
    }
};

複製程式碼
  • 解析:
    1. then方法可以接收兩個回撥函式作為引數; 1> Promise例項物件的狀態變為resolved時呼叫 2> Promsie例項的狀態變為rejected時呼叫[可選];
    2. then的成功回撥函式接收promise物件(也就是promise例項)的成功時的值作為引數(用於處理->執行);
    3. then的失敗回撥函式接收promise物件的失敗時的原因或錯誤作為引數;

Promise.prototype.then() 與非同步呼叫; 我們知道Promise主要是用來處理非同步的, 當promise物件的狀態在一段時間後發生變化時, 就會觸發then方法繫結的回撥函式; 這裡要注意幾點:

1> then方法在呼叫時會立即執行(同步), 關鍵在於then不同狀態的回撥函式只有在狀態發生改變時執行(非同步); 
2> Promise是同步的, 例項新建後會立即執行; then是非同步的,當狀態改變時才執行
複製程式碼
	let promise = new Promise(function(resolve, reject) {
	  console.log('Promise');
	  resolve();
	});
	
	promise.then(function() {
	  console.log('resolved.');
	});
	
	console.log('Hi!');
	// => 依次輸出: 'Promise'  'Hi!'  'resolved.'
複製程式碼
  • 使用:
let p = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve('成功');			// 2秒鐘之後,讓promise的狀態變成成功態
    }, 2000);
});

p.then((value)=>{
    // 成功時執行的回撥
    console.log('2秒後執行resolve,輸出例項成功態的vlaue屬性: ', value);
}, (reason)=>{
    // 失敗時執行的回撥
    console.log('執行reject,輸出例項失敗態的reason屬性: ', reason);
});
複製程式碼
  • 原始碼:
	function Promise(executor){
		let self = this;
	    self.status = 'pending';  // pending => fulfilled | rejected
	    self.value  = undefined;
	    self.reason = undefined;
	
	    self.onFulfilledCallbacks = [];     // 用於存放所有then方法成功態的回撥
	    self.onRejectedCallbacks = [];      // 用於存放所有then方法失敗態的回撥
	    // 成功 
	    function resolve(value){
	        if(self.status === 'pending'){
	            self.value  = value;
	            self.status = 'fulfilled';
	            self.onFulfilledCallbacks.forEach(fn=>fn()); // 當狀態變為resolved時執行訂閱的函式(then方法成功時的回撥)
	        }  
	    }
	
	    function reject(reason){
	        if(self.status === 'pending'){
	            self.reason = reason;
	            self.status = 'rejected';
	            self.onRejectedCallbacks.forEach(fn=>fn()); // 當狀態變為rejected時執行訂閱的函式(then方法失敗時的回撥)
	        }
	    }
	
	    try {
	        executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	    }catch(err){
	        reject(err);
	    }
	}
	
	
	Promise.prototype.then = function(onFulfilled, onRejected){
	    if(this.status === 'fulfilled'){
	        onFulfilled(this.value); // 這裡傳遞成功的值
	    }
	
	    if(this.status === 'rejected'){
	        onRejected(this.reason);  // 這裡傳遞失敗的原因或錯誤
	    }
	
		// 在非同步執行完成之前,promise的狀態為pending,此時會把then兩種狀態的回撥先儲存起來,待狀態改變後執行
	    if(this.status === 'pending'){
	        this.onFulfilledCallbacks.push(()=>{
	            onFulfilled(this.value);
	        });
	        this.onRejectedCallbacks.push(()=>{
	            onRejected(this.reason);
	        });
	    }
	};

複製程式碼
  • 解析:

    1. 回撥的儲存和呼叫是一個典型的釋出/訂閱模式;promise處於等待態時訂閱,狀態改變後執行釋出;
    2. 在非同步執行完成之前,此時promise的狀態為pending,此時我們把then函式的引數(成功的回撥resove和失敗reject的回撥)儲存起來,待非同步完成狀態改變後呼叫;
    3. 用於儲存then回撥的是兩個陣列,一個是成功回撥函式的陣列,一個是失敗回撥函式的陣列;
    4. 如果呼叫resolve函式和reject函式時帶有引數, 那麼它們的引數會被傳遞給then的回撥函式; 如果沒傳具體的引數,那就將undefined傳遞過去;
    5. reject函式的引數通常是Error例項,表示丟擲異常或錯誤;
    6. resolve函式的引數除了普通值以外, 還可以是另外一個Promise

then方法返回的是一個新的Promise例項(該例項不是原來那個Promise例項);

  • 使用(同步情況下的鏈式呼叫):
	let p = new Promise((resolve, reject)=>{
	    resolve(420);
	});
	
	p.then((value)=>{
	    // 成功時執行的回撥
	    console.log('2秒後執行resolve,輸出例項成功態的vlaue屬性: ', value); // 2秒後執行resolve,輸出例項成功態的vlaue屬性:  420
	    return value + 100;
	}, (reason)=>{
	    // 失敗時執行的回撥
	    console.log('執行reject,輸出例項失敗態的reason屬性: ', reason); 
	}).then((value)=>{
	    // then的成功回撥裡面輸出,前一個then成功回撥的返回值(即promise例項的value屬性), 如果沒有顯示的return, 那麼返回的是undefined
	    console.log('第二個then的成功回撥裡面輸出,前一個then成功回撥的返回值: ',value); // 第二個then的成功回撥裡面輸出,前一個then成功回撥的返回值:  520
	    return value + 200;
	}).then((value)=>{
	    console.log('第三個then的成功回撥裡面輸出,前一個then成功回撥的返回值: ',value);  // 第三個then的成功回撥裡面輸出,前一個then成功回撥的返回值:  720
	});

複製程式碼
  • 原始碼:
	function Promise(executor){
		
		let self = this;
	    self.status = 'pending';  // pending => fulfilled | rejected
	    self.value  = undefined;
	    self.reason = undefined;
	
	    self.onFulfilledCallbacks = [];     // 用於存放所有then方法成功態的回撥
	    self.onRejectedCallbacks = [];      // 用於存放所有then方法失敗態的回撥
	    // 成功 
	    function resolve(value){
	        if(self.status === 'pending'){
	            self.value  = value;
	            self.status = 'fulfilled';
	            self.onFulfilledCallbacks.forEach(fn=>fn());
	        }  
	    }
	
	    function reject(reason){
	        if(self.status === 'pending'){
	            self.reason = reason;
	            self.status = 'rejected';
	            self.onRejectedCallbacks.forEach(fn=>fn());
	        }
	    }
	
	    try {
	        executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	    }catch(err){
	        reject(err);
	    }
	}
	
	
	Promise.prototype.then = function(onFulfilled, onRejected){
	    let self = this;
	    let promise2 = new Promise((resolve, reject)=>{
	        console.log(this === self); // 這裡因為使用了箭頭函式,所以self === this, 指向同一個物件; 為了不混淆,後面將採用self的方式
	        // 該executor函式會裡面執行,因此把之前的狀態判斷及成功回撥的程式碼移到此處與之前功能一樣;
	        // 需要說明的是這個promise需要根據上一個then的狀態和值進行判斷,故而設定self變數用於引用上一個this
	        if(this.status === 'fulfilled'){
	            let x = onFulfilled(this.value); // 成功回撥; 這裡的value指的是第一個Promise例項的value屬性
	            resolve(x);   // 將處理後的結果作為引數傳遞給promise例項的value屬性; 同時也會傳遞給下一個then的成功回撥
	        }
	    
	        if(this.status === 'rejected'){
	            try{
	                let x = onRejected(this.reason);  // onRejected處理 
	                reject(x);
	            }catch(e){
	                reject(e);
	            }
	
	        }
	    
	        if(this.status === 'pending'){
	            this.onFulfilledCallbacks.push(()=>{
	                onFulfilled(this.value);
	            });
	            this.onRejectedCallbacks.push(()=>{
	                onRejected(this.reason);
	            });
	        }
	    });
	
	
	    return promise2;
	};
複製程式碼
  • 解析:

    1. then鏈式呼叫的關鍵是:then方法呼叫後返回一個新的Promise例項;
    2. 生成promise2例項時傳遞到Promise建構函式中的執行器excutor, 它裡面用於判斷狀態的是上一個promise例項的狀態,操作的都是上一個promise例項的屬性;
    3. 有多少個then,就至少有多少個promise例項;
    4. 不論有多少個promise例項,在new新的Promise例項時都是判斷前一個promise例項的狀態、操作前一個promise例項的屬性, 把前面的promise例項上的屬性更新後, 傳遞到後面的then回撥函式裡面;
    5. 無論多少個then,都是前一個回撥函式完成以後,將返回結果作為引數,傳入下一個then的對應的回撥中;
    6. 無論多少個then,某一狀態的回撥都是剛在一個陣列,然後挨個的執行; 前一個執行完成後把例項屬性傳遞給下一個回撥函式

Promise.prototype.then() 與非同步呼叫

  • 使用(非同步情況下的鏈式呼叫):
let p = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve(420);
    }, 3000);
});

p.then((value)=>{
    // 成功時執行的回撥
    console.log('2秒後執行resolve,輸出例項成功態的vlaue屬性: ', value); // 2秒後執行resolve,輸出例項成功態的vlaue屬性:  420
    return value + 100;
}, (reason)=>{
    // 失敗時執行的回撥
    console.log('執行reject,輸出例項失敗態的reason屬性: ', reason); 
    throw new Error('失敗了');
}).then((value)=>{
    // then的成功回撥裡面輸出,前一個then成功回撥的返回值(即promise例項的value屬性), 如果沒有顯示的return, 那麼返回的是undefined
    console.log('第二個then的成功回撥裡面輸出,前一個then成功回撥的返回值: ',value); // 第二個then的成功回撥裡面輸出,前一個then成功回撥的返回值:  520
    return value + 200;
}, (reason)=>{
    console.log('第二個then的失敗: ', reason);
}).then((value)=>{
    console.log('第三個then的成功回撥裡面輸出,前一個then成功回撥的返回值: ',value);  // 第三個then的成功回撥裡面輸出,前一個then成功回撥的返回值:  720
});

// => 
		2秒後執行resolve,輸出例項成功態的vlaue屬性:  420
		第二個then的成功回撥裡面輸出,前一個then成功回撥的返回值:  520
		第三個then的成功回撥裡面輸出,前一個then成功回撥的返回值:  720
複製程式碼
  • 原始碼
	function Promise(executor){
		let self 	= this;
	    self.status = 'pending';  // pending => fulfilled | rejected
	    self.value  = undefined;
	    self.reason = undefined;
	    self.onFulfilledCallbacks = [];     // 用於存放所有then方法成功態的回撥
	    self.onRejectedCallbacks = [];      // 用於存放所有then方法失敗態的回撥
	    // 成功 
	    function resolve(value){
	        if(self.status === 'pending'){
	            self.value  = value;
	            self.status = 'fulfilled';
	            self.onFulfilledCallbacks.forEach(fn=>fn());
	        }  
	    }
	
	    function reject(reason){
	        if(self.status === 'pending'){
	            self.reason = reason;
	            self.status = 'rejected';
	            self.onRejectedCallbacks.forEach(fn=>fn());
	        }
	    }
	
	    try {
	        executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	    }catch(err){
	        reject(err);
	    }
	}
	
	
	Promise.prototype.then = function(onFulfilled, onRejected){
	    let self = this;
	    let promise2 = new Promise(function(resolve, reject){
	        // console.log(this === self); // 這裡因為使用了箭頭函式,所以self === this, 指向同一個物件
	        // 該executor函式會裡面執行,因此把之前的狀態判斷及成功回撥的程式碼移到此處與之前功能一樣;
	        // 需要說明的是這個promise需要根據上一個的狀態和值進行判斷,故而設定self變數用於引用上一個this
	        if(self.status === 'fulfilled'){
	            let x = onFulfilled(self.value); // 成功回撥; 如果沒有就當前物件的value屬性值
	            resolve(x);   // 將處理後的結果作為引數傳遞給promise例項的value屬性; 同時也會傳遞給下一個then的成功回撥
	        }
	    
	        if(self.status === 'rejected'){
	            try{
	                let x = onRejected(self.reason);  // onRejected處理
	                reject(x);
	            }catch(e){
	                reject(e);
	            }
	        }
	    
	        if(self.status === 'pending'){
	            self.onFulfilledCallbacks.push(()=>{
	                let x = onFulfilled(self.value);
	                resolve(x);
	            });
	            self.onRejectedCallbacks.push(()=>{
	                let x = onRejected(self.reason);
	                reject(x);
	            });
	        }
	    });
	    return promise2;
	};
複製程式碼
  • 解析

    1. 非同步和同步的處理方式幾乎一致, 非同步的無非就是在pending態時先把回撥儲存起來,待狀態改變時再執行

Promise.prototype.then()的成功回撥返回一個新的promise例項和執行executor時resolve一個新的promsie例項

採用鏈式的then,可以指定一組按照次序呼叫的回撥函式; 此時前一個回撥函式,有可能返回的還是一個Promise物件(即有非同步操作),這時後一個回撥函式,就會等待該Promise物件的狀態發生變化,才會被呼叫;

因此處邏輯\程式碼比較多,放在一起看了

  • 使用
	let p = new Promise((resolve, reject)=>{
	    resolve(1000);
		// 或 resolve(new Promise((resolve, reject)=>{ resolve('成功');}))
	});
	
	p.then((value)=>{
	    return new Promise((resolve, reject)=>{
	        resolve(value + 500);
	    });
	}).then((value)=>{
	    console.log(value);  // 1500
	});
複製程式碼
  • 原始碼
	function Promise(executor){
	    let self = this;
	    this.status = 'pending';  // pending => fulfilled | rejected
	    this.value  = undefined;
	    this.reason = undefined;
	    this.onFulfilledCallbacks = [];     // 用於存放所有then方法成功態的回撥
	    this.onRejectedCallbacks = [];      // 用於存放所有then方法失敗態的回撥
	
	    // 成功 
	    function resolve(value){
	        // 如果value是個Promise例項, 就要先處理該promise例項
	        /*
	        let p = new Promise((resolve, reject)=>{
	            resolve(new Promise(function(resolve, reject){
	                resolve('成功');
	            }));
	        })
	        */
	
	        if(value instanceof Promise){
	            return value.then((data)=>{
	                resolve(data);
	            },(y)=>{
	                reject(y);
	            });
	        }
	
	
	        if(self.status === 'pending'){
	            self.value  = value;
	            self.status = 'fulfilled';
	            self.onFulfilledCallbacks.forEach(fn=>fn());
	        }  
	    }
	
	    function reject(reason){
	        if(self.status === 'pending'){
	            self.reason = reason;
	            self.status = 'rejected';
	            self.onRejectedCallbacks.forEach(fn=>fn());
	        }
	    }
	
	    try {
	        executor(resolve, reject);  // 該函式會在new Promise()例項時立即呼叫,它接收兩個引數: resolve reject
	    }catch(err){
	        reject(err);
	    }
	    
	}
	
	
	function resolvePromise(x, promise2, resolve, reject){
	    // 如果then的回撥函式中返回之前的promsie,就有問題了(因為狀態一旦改變就被凍結,不能再次變化))
	    if(x === promise2){
	        return reject(new TypeError('迴圈應用'));
	    } 
	
	    // 判斷x是普通值還是物件,如果是普通值,直接resolve
	    // 如果是物件或函式執行
	    if((x !== null && typeof x === 'object') || typeof x === 'function'){
	        // 這裡可以是promise,然後嘗試執行
	        try {
	            // 判斷有沒有then方法, 在獲取then的過程中也可能會出錯(比如某個物件的then屬性get的時候丟擲錯誤)
	            /*
	            let obj = {};
	            Object.defineProperty(obj, 'then', {
	                get(){
	                    throw new Error('不讓你get!');
	                }
	            });
	            */
	            let then = x.then;   // 獲取x的then屬性; 如果沒有就會丟擲錯誤
	            if(typeof then === 'function'){
	                then.call(x, (y)=>{
	                    // y有可能也是一個promise
	                    // 遞迴解析,直到結果是普通值為止
	                    resolvePromise(y, promise2, resolve, reject);
	                }, (r)=>{
	                    reject(r);
	                });
	            } else {  
	                // 有可能是普通物件或普通值 {then: 'xbs'}或{then: {}}
	                resolve(x);
	            }
	        }catch(e){
	            // 沒有then那可能就是一個普通物件{a:xxx}
	            reject(e);
	        }
	    } else {
	        resolve(x);
	    }
	    
	}
	
	Promise.prototype.then = function(onFulfilled, onRejected){
	    // .then().then().then()值的穿透, 因為我們在then沒傳回撥引數時,手動給其新增了相應的回撥函式
	    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value=> value;
	    onRejected = typeof onRejected === 'function' ? onRejected: err => {throw err};
	    let self = this;
	    let promise2 = new Promise(function(resolve, reject){
	        // 該executor函式會立刻執行,因此把之前的狀態判斷及成功回撥的程式碼移到此處與之前功能一樣;
	        // 需要說明的是這個promise需要根據上一個的狀態和值進行判斷,故而設定self變數用於引用上一個this
	        if(self.status === 'fulfilled'){
	            // 這裡要使用promise2, 所以需要增非同步保證可以獲取到promise2;
	            // 為什麼要使用promise2? - 因為每次then要返回的是一個新的promise, 如果有人要返回上一個promise呢, 這時候就需要去判斷,promise2和x的關係
	            /*
	                // 看這裡就知道為什麼要判斷x與promise2的關係了
	                let p = new Promise((resolve,reject)=>{
	                    resolve('成功');
	                })
	                p.then((value)=>{
	                    return p;
	                }); 
	            */
	
	            // 使用定時器是為了保障promise2能獲取到; (先執行同步程式碼)非同步程式碼(setTimeout是巨集任務,主棧程式碼執行完畢,微任務執行後再執行)是在同步執行完成後執行,故而可以獲取到promise2
	            setTimeout(function(){   
	                // 為什麼要try呢? 因為沒人能保證櫻花大道上沒有狗狗的翔 
	                /*
	                    let p = new Promise((resolve,reject)=>{
	                        resolve('成功');
	                    })
	                    p.then((value)=>{
	                        throw new Error('就欺負你怎麼了');  // 成功態中拋錯誤,可謂防不勝防啊
	                    }, (reason)=>{
	                        // 失敗回撥...
	                    })
	                */
	                try {
	                    let x = onFulfilled(self.value); 
	                    // 如果此次x是返回的新的promise如何處理? 
	                    // 採用統一的方法來處理x,判斷x是promise還是普通值
	                    resolvePromise(x, promise2, resolve, reject);
	                } catch(err) {
	                    // 如果執行函式時丟擲失敗 那麼會走向下一個then的失敗狀態
	                    reject(err);
	                }                        
	            }, 0);
	        }
	    
	        if(self.status === 'rejected'){
	            setTimeout(function(){
	                try {
	                    let x = onRejected(self.reason);  // onRejected處理
	                    resolvePromise(x, promise2, resolve, reject);
	                } catch(err) {
	                    // 如果執行函式時丟擲失敗 那麼會走向下一個then的失敗狀態
	                    reject(err);
	                }  
	            }, 0)
	        }
	    
	        if(self.status === 'pending'){
	            // 非同步的處理在這裡
	            // 因為需要待非同步執行完成後呼叫執行,而何時呼叫並不知道; 因此要先存起來(訂閱),待狀態改變再執行(釋出)
	            self.onFulfilledCallbacks.push(()=>{
	                setTimeout(()=>{
	                    // 同樣也會遇到成功態回撥裡面丟擲錯誤的情況,所以也要try{}catch(){}一下
	                    try{
	                        let x = onFulfilled(self.value);
	                        resolve(x); 
	                    }catch(err){
	                        reject(err);
	                    }
	                },0);
	            });
	            self.onRejectedCallbacks.push(()=>{
	                setTimeout(()=>{
	                    // 同樣也會遇到成功態回撥裡面丟擲錯誤的情況,所以也要try{}catch(){}一下
	                    try{
	                            let x = onRejected(self.reason);
	                            reject(x);
	                        }catch(err){
	                            reject(err);
	                        }
	                    },0);
	            });
	        }
	    });
	    return promise2;
	};

複製程式碼
  • 解析

其實到這裡,Promise的核心已經實現了


Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回撥函式

  • 使用
	getJSON('/posts.json').then(function(posts) {
	  // ...
	}).catch(function(error) {
	  // 處理 getJSON 和 前一個回撥函式執行時發生的錯誤
	  console.log('發生錯誤!', error);
	});
複製程式碼
  • 原始碼
	Promise.prototype.catch = function(errCallback){
	    return this.then(null, errCallback);
	};

複製程式碼

Promise.all

Promise.all方法用於將多個Promise例項,包裝成一個新的Promise例項

  • 使用
	let p1 = new Promise((resolve, reject)=>{
	    resolve('p1-success!');
	});
	
	let p2 = new Promise((resolve, reject)=>{
	    setTimeout(()=>{
	        resolve('p2-success!');
	    }, 2000);
	});
	
	let p3 = new Promise((resolve, reject)=>{
	    setTimeout(()=>{
	        resolve('p3-success!');
	    }, 3000);
	});
	
	let p = Promise.all([p1, p2, p3]).then((result)=>{
	    console.log(result);	// [ 'p1-success!', 'p2-success!', 'p3-success!' ]
	});

複製程式碼
  • 原始碼
	Promise.all = function(values){
	    return new Promise(function(resolve, reject){
	        let results = [];	// 存放結果
	        let index = 0;		// 處理方法執行了幾次
	        function processData(resIndex, data){
	            index++;
	            results[resIndex] = data;		// 將執行結束後的結果存放到結果陣列(因為結果和執行順序有嚴格對應關係,所以不能用push,用arr[0] = value的形式);
	            if(index === values.length){
	                resolve(results); 		// 當結果陣列和執行操作的數量一樣時,將結果返回
	            }
	        }
	        
	        for(let i = 0; i < values.length; i++){
	            let current = values[i];
	            if(current && current.then && typeof current.then === 'function'){
	                // promise
	                current.then(y=>{
	                    processData(i, y);
	                },reject)
	            } else {
	                processData(i, current); // 如果是普通值,直接返回
	            }
	        }
	    });
	};

複製程式碼
  • 解析

    1. Promise.all的實現原理的核心是計數器

Promise.race

  • 使用
	let p1 = new Promise((resolve, reject)=>{
	    resolve('p1-success!');
	});
	
	let p2 = new Promise((resolve, reject)=>{
	    setTimeout(()=>{
	        resolve('p2-success!');
	    }, 2000);
	});
	
	let p3 = new Promise((resolve, reject)=>{
	    setTimeout(()=>{
	        resolve('p3-success!');
	    }, 3000);
	});
	
	let p = Promise.race([p1, p2, p3]).then((result)=>{
	    console.log(result); //'p1-success!'
	});

複製程式碼
  • 原始碼
	Promise.race = function(values){
	    return new Promise((resolve, reject)=>{
	        for(let i = 0; i < values.length; i++){
				let current = values[i];
	            if(current && current.then && typeof current.then === 'function'){
	                current.then(resolve,reject);
	            } else {
	                resolve(current);
	            }
	        }
	    });
	};

複製程式碼

Promise.resolve()

Promise.resolve 可以將現有物件轉換為Promise物件

  • 使用
	let p = Promise.resolve(300);
	console.log(p instanceof Promise); // true
複製程式碼
  • 原始碼
	Promise.resolve = function(value){
	    return new Promise(function(resolve,reject){
	        resolve(value);
	    });
	};
複製程式碼

Promise.reject()

Promise.reject 可以將現有物件轉換為Promise物件

  • 使用
	let p = Promise.reject(300);
	console.log(p instanceof Promise); // true
複製程式碼
  • 原始碼
	Promise.reject = function(reason){
	    return new Promise(function(resolve,reject){
	        reject(reason);
	    });
	};
複製程式碼

結語

先告一段落啦, 因理解能力有限, 難免會有遺漏和偏差,如果您發現了請告知! 學習和成長的路上看了很多大佬的部落格\視訊\文件\程式碼,一直在消費大佬的辛苦成果,自己寫一寫,算是向大佬致敬了!

相關文章