深刻理解ES6--11.Promise與異步編程

主要知識點:Promise生命週期、Promise基本操做、Promise鏈、響應多個Promise以及集成Promise編程

Promise與異步編程知識點

1. Promise基礎

什麼是回調地獄?數組

當使用回調函數來進行事件處理的時候,若是嵌套多層回調函數的時候,就會出現回調地獄,例如:promise

method1(function(err, result) {
	if (err) {
		throw err;
	} 
	method2(function(err, result) {
		if (err) {
			throw err;
		} 
		method3(function(err, result) {
			if (err) {
				throw err;
			} 
			method4(function(err, result) {
				if (err) {
					throw err;
				} 
				method5(result);
			});
		});
	});
});
複製代碼

像本例同樣嵌套多個方法調用會建立錯綜複雜的代碼,會難以理解與調試。當想要實現更復 雜的功能時,回調函數也會存在問題。要是你想讓兩個異步操做並行運行,而且在它們都結 束後提醒你,那該怎麼作?要是你想同時啓動兩個異步操做,但只採用首個結束的結果,那 又該怎麼作?而使用Promise就能避免回調地獄的狀況。異步

Promise能夠當作是一個佔位符,表示異步操做的執行結果。函數能夠返回一個Promise,而沒必要訂閱一個事件或者向函數傳遞一個回調函數。異步編程

Promise的生命週期函數

每一個 Promise 都會經歷一個短暫的生命週期,初始爲掛起狀態(pending state) ,這表示異步操做還沒有結束。一個掛起的 Promise 也被認爲是未決的(unsettled )。一旦異步操做結束, Promise就會被認爲是已決的(settled ) ,並進入兩種可能狀態之一:this

  1. 已完成(fulfilled ) : Promise 的異步操做已成功結束;
  2. 已拒絕(rejected ) : Promise 的異步操做未成功結束,多是一個錯誤,或由其餘緣由致使。

內部的[[PromiseState]] 屬性會被設置爲"pending""fulfilled" 或 "rejected",以反映Promise的狀態。該屬性並未在 Promise 對象上被暴露出來,所以你沒法以編程方式判斷 Promise 到底處於哪一種狀態。不過你可使用then()方法在 Promise 的狀態改變時執行一些特定操做。spa

  1. then()方法調試

    then()方法在全部的 Promise 上都存在,而且接受兩個參數。第一個參數是 Promise 被完成時要調用的函數,異步操做的結果數據都會被傳入這個完成函數。第二個參數則是 Promise 被拒絕時要調用的函數,與完成函數類似,拒絕函數會被傳入與拒絕相關聯的任何附加數據。then()方法的兩個參數是可選的,所以能夠自由組合監聽完成和失敗的處理函數;code

  2. catch()方法

    Promise有catch()方法,等同於只傳遞拒絕處理函數給then()方法:

    promise.catch(function(err) {
     	// 拒絕
     	console.error(err.message);
     });
     // 等同於:
     promise.then(null, function(err) {
     	// 拒絕
     	console.error(err.message);
     });
    複製代碼

建立未決的Promise

使用Promise構造器能夠建立一個Promise實例,此構造器接收一個參數:一個被稱之爲執行器(excutor)的函數,該函數包含了resolve()函數和reject()函數這兩個參數。resolve()函數在異步任務執行成功時調用,而reject()函數在異步任務執行失敗時調用。例如:

let promise = new Promise(function(resolve,reject){
	console.log('hi, promise');
	resolve();

});

promise.then(()=>{
	console.log('hi, then');

});

console.log('hi');

輸出:
hi, promise
hi
hi then
複製代碼

從輸出結果能夠看出,Promise構造器中的代碼是最早執行的,而then()代碼是最後執行的,這是由於只有在Promise中的處理器函數執行結束以後,then()方法中的完成處理函數或者拒絕處理函數纔會添加到做業隊列的尾部。

建立已決的Promise

  1. 使用Promise.resolve()

Promise.resolve()方法接收一個參數,並會返回一個處於已完成狀態的 Promise ,在then()方法中使用完成處理函數才能提取該完成態的Promise傳遞的值,例如:

let promise = Promise.resolve('hi');
promise.then((value)=>{
	console.log(value); //hi
});
複製代碼
  1. 使用Promise.reject()

可使用Promise.reject()方法來建立一個已拒絕狀態的Promise,一樣只有在拒絕處理函數中或者catch()方法中才能接受reject()方法傳遞的值:

let reject = Promise.reject('reject');

reject.catch((value)=>{
	console.log(value); //reject
})
複製代碼

非Promise的thenable

當一個對象擁有一個能接受resolvereject參數的then()方法時,該對象就會被認爲是一個非Promisethenable,例如:

let thenable = {

	then:function(resolve,reject){
		resolve('hi');
	}
}
複製代碼

Promise.resolve()Promise.reject()方法都可以接受非Promise的thenable做爲參數,當傳入了非Promise的thenable時,這些方法會建立一個新的Promise,而且可使用then()方法對不一樣狀態進行操做:

建立一個已完成的Promise

let thenable = {

	then:function(resolve,reject){
		resolve('hi');
	}
}

let promise = Promise.resolve(thenable);
promise.then((value)=>{
	console.log(value); //hi
});
複製代碼

一樣利用thenable能夠建立一個已拒絕的Promise:

let thenable = {

	then:function(resolve,reject){
		reject('hi');
	}
}

let promise = Promise.resolve(thenable);
promise.then(null,(value)=>{
	console.log(value);
});
複製代碼

執行器錯誤

當執行器內部拋出錯誤,那麼Promise的拒絕處理函數就會被調用,例如:

let promise = new Promise(function(resolve,reject){
	throw new Error('Error!');

})

promise.catch(function(msg){
	console.log(msg); //error
})
複製代碼

2. Promise鏈

除了使用單個Promise外,多個Promise能夠進行級聯使用,實際上then()方法或者catch()方法會返回一個新的Promise,僅當前一個Promise被決議以後,後一個Promise纔會進行處理。

串聯Promise

let p1 = new Promise(function(resolve,reject){
	resolve('hi');
});

p1.then((value)=>{
	console.log(value);
	throw new Error('Error!');
}).catch(function(error){
	console.log(error);
})
複製代碼

能夠看出當p1的then()方法執行結束後會返回一個Promise,所以,在此基礎上能夠繼續執行catch()方法。同時,Promise鏈容許捕獲前一個Promise的錯誤

Promise鏈中傳遞值

**Promise鏈的另外一個重要方面是能從一個Promise傳遞數據給另外一個Promise的能力。**前一個Promise的完成處理函數的返回值,傳遞到下一個Promise中。

//Promise鏈傳遞值

let p1 = new Promise(function(resolve,reject){
	resolve(1);
})

p1.then(value=>value+1)
.then(value=>{
	console.log(value);
})
複製代碼

p1的完成處理函數返回了value+1,也就是2,會傳入到下一個Promise的完成處理函數,所以,第二個then()方法中的完成處理函數就會輸出2。拒絕處理函數一樣能夠被用於在Promise鏈中傳遞數據。

Promise鏈中傳遞Promise

在完成或者拒絕處理函數中能夠返回基本類型值,從而能夠在Promise鏈中傳遞。另外,在Promise鏈中也能夠傳遞對象,若是傳遞的是Promise對象,就須要額外的處理:

傳遞已完成狀態的Promise

let p1 = new Promise(function(resolve,reject){
	resolve(1);
});

let p2 = new Promise(function(resolve,reject){
	resolve(2);
})

p1.then(value=>{
	console.log(value);
	return p2;
}).then(value=>{
	console.log(value);
});
輸出:1  2
複製代碼

p1中返回了Promise對象p2,當p2完成時,纔會調用第二個then()方法,將值value傳到完成處理函數中。若Promise對象p2被拒絕後,第二個then()方法中的完成處理函數就不會執行,只能經過拒絕處理函數才能接收到p2傳遞的值:

let p1 = new Promise(function(resolve,reject){
	resolve(1);
});

let p2 = new Promise(function(resolve,reject){
	reject(2);
})

p1.then(value=>{
	console.log(value);
	return p2;
}).catch(value=>{
	console.log(value);
});
複製代碼

3. 響應多個Promise

若是想監視多個Promise的狀態,從而決定下一步動做,可使用ES6提供的兩個方法:Promise.all()Promise.race()

Promise.all()

Promise.all()方法能接受單個可迭代對象(如數組)做爲參數,可迭代對象的元素都是Promise。該方法會返回一個Promise,只有傳入全部的Promise都已完成,所返回的Promise纔會完成,例如:

//Promise.all()
let p1 = new Promise(function(resolve,reject){
	resolve(1);
})

let p2 = new Promise(function(resolve,reject){
	resolve(2);
})

let p3 = new Promise(function(resolve,reject){
	resolve(3);
})

let p4 = Promise.all([p1,p2,p3]);
p4.then(value=>{
	console.log(Array.isArray(value)); //true
	console.log(value); //[1,2,3]
})
複製代碼

Promise.all() 的調用建立了新的Promise p4,在 p1p2p3 都被完成後, p4 最終會也被完成。傳遞給 p4 的完成處理函數的結果是一個包含每一個決議值(1 、 2 與 3 ) 的數組,這些值的存儲順序保持了待決議的 Promise 的順序(與完成的前後順序無關) ,所以你能夠將結果匹配到每一個Promise

若傳遞給Promise.all() 的某個 Promise 被拒絕了,那麼方法所返回的 Promise 就會馬上被拒絕,而沒必要等待其餘的 Promise 結束

//Promise.all()
let p1 = new Promise(function(resolve,reject){
	resolve(1);
})

let p2 = new Promise(function(resolve,reject){
	reject(2);
})

let p3 = new Promise(function(resolve,reject){
	resolve(3);
})

let p4 = Promise.all([p1,p2,p3]);
p4.catch(value=>{
	console.log(Array.isArray(value)); //true
	console.log(value); //2
})
複製代碼

在此例中, p2 被使用數值 2 進行了拒絕,則 p4 的拒絕處理函數就馬上被調用,而不會 等待 p1 或 p3 結束執行(它們仍然會各自結束執行,只是 p4 不等它們) 。

拒絕處理函數總會接受到單個值,而不是一個數組。該值是被拒絕的Promise所返回的拒絕值。

Promise.race()

Promise.race()方法接收一個元素是Promise的可迭代對象,並返回一個新的Promise。一旦傳入Promise.race()的可迭代對象中有一個Promise是已決狀態,那麼返回的Promise對象就會馬上成爲已決狀態。

Promise.all()方法得必須等到全部傳入的Promise所有變爲已決狀態,所返回的Promise纔會已決。

let p1 = new Promise(function(resolve,reject){
	resolve(1);
})

let p2 = new Promise(function(resolve,reject){
	resolve(2);
})

let p3 = new Promise(function(resolve,reject){
	resolve(3);
})

let p4 = Promise.race([p1,p2,p3]);
p4.then(value=>{
	console.log(Array.isArray(value)); //false
	console.log(value); //1
})
複製代碼

Promise.race() 方法傳入的Promise中哪個Promise先變成已完成狀態,就會將值傳遞給所返回的Promise對象的完成處理函數中。若哪個Promise最早變成已拒絕狀態,一樣的,會將值傳遞給p4的拒絕處理函數中。

4. 繼承Promise

能夠繼承Promise實現自定義的Promise,例如:

class MyPromise extends Promise {
	// 使用默認構造器
	success(resolve, reject) {
		return this.then(resolve, reject);
	} 
	failure(reject) {
		return this.catch(reject);
	}
} 
let promise = new MyPromise(function(resolve, reject) {
	resolve(42);
});
promise.success(function(value) {
	console.log(value); // 42
}).failure(function(value) {
	console.log(value);
});
複製代碼

在此例中, MyPromise 從 Promise 上派生出來,並擁有兩個附加方法。 success() 方法模擬了 resolve()failure() 方法則模擬了 reject()

5. 總結

  1. Promise 具備三種狀態:掛起、已完成、已拒絕。一個 Promise 起始於掛起態,並在成功時轉爲完成態,或在失敗時轉爲拒絕態。 then() 方法容許你綁定完成處理函數與拒絕處理函數,而 catch() 方法則只容許你綁定拒絕處理函數;

  2. 可以將多個Promise串聯起來組成Promise鏈,而且可以在中間傳遞值,甚至是傳遞Promise對象。 then() 的調用都建立並返回了一個新的 Promise ,只有在前一個 Promise 被決議過,新 Promise 也會被決議。 同時也可使用Promise.all()和Promise.race()方法來管理多個Promise。

相關文章
相關標籤/搜索