深入理解ES6--11.Promise與非同步程式設計

你聽___發表於2018-05-03

深入理解ES6--11.Promise與非同步程式設計

主要知識點:Promise生命週期、Promise基本操作、Promise鏈、響應多個Promise以及整合Promise

Promise與非同步程式設計知識點

1. 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 ) ,並進入兩種可能狀態之一:

  1. 已完成(fulfilled ) : Promise 的非同步操作已成功結束;
  2. 已拒絕(rejected ) : Promise 的非同步操作未成功結束,可能是一個錯誤,或由其他原因導致。

內部的[[PromiseState]] 屬性會被設定為"pending""fulfilled" 或 "rejected",以反映Promise的狀態。該屬性並未在 Promise 物件上被暴露出來,因此你無法以程式設計方式判斷 Promise 到底處於哪種狀態。不過你可以使用then()方法在 Promise 的狀態改變時執行一些特定操作。

  1. then()方法

    then()方法在所有的 Promise 上都存在,並且接受兩個引數。第一個引數是 Promise 被完成時要呼叫的函式,非同步操作的結果資料都會被傳入這個完成函式。第二個引數則是 Promise 被拒絕時要呼叫的函式,與完成函式相似,拒絕函式會被傳入與拒絕相關聯的任何附加資料。then()方法的兩個引數是可選的,因此可以自由組合監聽完成和失敗的處理函式;

  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。

相關文章