1. 程式人生 > >ES6中的Promise物件

ES6中的Promise物件

什麼是 Promise : 簡單說Promise 就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果. 從語法上說,Promise是一個物件,從他可以獲取非同步操作的訊息. Promise 是非同步程式設計的一種解決方案,比傳統的解決方案–回撥函式和事件–更合理和更強大. Promise 物件有兩個特點:

  1. 物件的狀態不受外界影響. Promise 物件代表一個非同步操作,有三種狀態: pending(進行中),fulfilled(已成功)和rejected(已失敗). 只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態.
  2. 一旦狀態改變,就不會再變,任何時候都可以得到這個結果. Promise 物件的狀態改變,只有兩種可能: 從 pending變為 fulfilled和從 pending 變為reject . 只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為resolved(已定型). 如果改變已經發生,你再對Promise 物件添加回調函式,也會立即得到這個結果. 這與事件完全不同,事件的特點是.如果你錯過了他,再去監聽,是得不到結果的.
    有了Promise 物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式,此外,Promise 物件提供統一的介面,使得控制非同步操作更加容易. **缺點:**首先無法取消Promise,一旦新建它就會立即執行,無法中途取消.其次,如果不設定回撥函式,Promise 內部丟擲的錯誤,不會反應到外部. 第三,當 pending 狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)

基本用法: ES6規定, Promise 物件是一個建構函式,用來生成Promise例項. 下面是一個 Promise 例項:

const promise = new Promise(function (resolve,reject) {
	// ... some code
	if(/*非同步操作成功*/){
		resolve(value);
	}else {
		reject(error);
	}
})

Promise 建構函式接受一個函式作為引數,該函式的兩個引數分別是 resolve 和 reject . 他們是兩個函式,由js 引擎提供,不用自己部署. resolve 函式的作用是,將 Promise 物件的狀態從 ‘未完成’變為’成功’(即從pending變成resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去; reject 函式的作用是,將Promise 物件的狀態從’未完成’變為’失敗’(即從pending變為rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去.

Promise 例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式.

promise.then(function(value) {
	//success
},  function(error) {
	//failure
})

then 方法可以接受兩個回撥函式作為引數. 第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件狀態變為rejected時呼叫. 其中第二個函式是可選的,不一定提供. 這兩個函式都接受Promise物件傳出的值作為引數.

下面介紹一個Promise物件的簡單例子:

function timeout(ms) {
	return new Promise((resolve,reject) => {
		setTimeout(resolve,ms,'done');
	});
}
timeout(100).then ((value) => {
	console.log(value);
})

上面程式碼中,timout方法返回一個Promise例項,表示一段時間以後才會發生的結果.過了特定的時間(ms引數)以後,Promise例項的狀態變為resolved,就會觸發then方法繫結的回撥函式.

Promise 新建後就會立即執行:

let promise = new Promise(function(resolve,reject) {
	console.log('Promise');
	resolve();
});
promise.then(function() {
	console.log('resolved.');
});
console.log('Hi!');

//Promise
//Hi
//resolved.

上面程式碼中,Promise新建後立即執行,所以首先輸出的是 Promise,然後,then 方法指定的回撥函式,將在當前指令碼所有同步任務執行完才會執行,所以resolved最後輸出.

下面是非同步載入圖片的例子: function loadImageAsync(url) { return new Promise(function (resolve,reject) { const image = new Image(); image.onload = function() { reject(new Error(‘Could not load image at’ + url)); }; image.src = url; }) } 上面程式碼中,使用Promise 包裝了一個圖片載入的非同步操作.如果載入成功,就呼叫resolve方法,否則就呼叫reject方法.

下面是一個用 Promise 物件實現的Ajax操作的例子:

const getJSON = function(url) {
	const promise = new Promise(function(resolve,reject) {
		const handler = function() {
			if(this.readyState !== 4) {
				return;
			}
			if(this.status === 200) {
				resolve(this.response);
			}else{
				reject(new Error(this.statusText));
			}
		};
		const client = new XMLHTTPRequest();
		client.open("GET",url);
		client.onreadystatechange = handler;
		client.responseType = "json";
		client.setRequestHeader("Accept","application/json");
		client.send();
	});
	return promise;
};
getJSON("/posts.json").then(function(json) {
	console.log("Contents" + json);
},  function(error) {
    console.log("出錯了",error);
});

上面程式碼中,getJSON是對 XMLHTTPRequest 物件的封裝,用於發出一個針對JSON資料的HTTP請求,並且返回一個Promise物件. 需要注意的是,在getJSON內部,resolve函式和reject函式呼叫時,都帶有引數.

注意,呼叫resolve或reject 並不會終結Promise的引數函式的執行.

new Promise((resolve,reject) => {
	resolve(1);
	console.log(2);
}).then(r => {
	console.log(r);
});
//2
//1

上面程式碼中,呼叫resolve(1)之後,後面的console.log(2)還是會執行,並且會首先打印出來.這是因為立即resolved的Promise是在本輪事件迴圈的末尾執行,總是晚於本輪迴圈的同步任務.

一般來說,呼叫resolve 或reject 以後,Promise的使命就完成了,後繼操作應該放到then方法裡面,而不應該直接寫在resolve或reject 的後面.所以,最好在他們前面加上return 語句,這樣就不會有意外.

new Promise((resolve,reject) => {
	return resolve(1);
	//後面的語句不會執行
	console.log(2);
})

Promise.prototype.then() 它的作用是為Promise例項新增狀態改變時的回撥函式. 前面說過,then方法的第一個引數是resolve狀態的回撥函式,第二個引數(可選)是rejected狀態的回撥函式. then方法返回的是一個新的Promise例項(注意,不是原來那個Promise例項).因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法.

getJSON("posts.json").then(function(json) {
	return json.post;
}).then(function(posts) {
	// ...
})

上面的程式碼使用then 方法,依次指定了兩個回撥函式.第一個回撥函式完成以後,會將返回的結果作為引數,傳入第二個回撥函式.

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

getJSON("/post/1.json").then(function(post) {
	return getJSON(post.commentURl);
}).then(function funcA(comment) {
	console.log("resolved",comments);
}, function funcB(err){
	console.log('rejected',err);
});

上面程式碼中,第一個then 方法指定的回撥函式,返回的是另一個Promise物件.這時第二個then方法指定的回撥函式,就會等待這個新的Promise物件狀態發生變化.如果變為resolved,就會呼叫funcA,如果狀態變為rejected,就會呼叫funcB. 如果採用箭頭函式,上面的程式碼可以寫的更簡潔.

getJSON("/post/1.json").then(
	post => getJSON(post.commentURl)
).then(
	comments => console.log("resolved", comments),
	err => console.log("rejected",err)
);

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

const p = Promise.all([ p1, p2, p3 ]);

上面程式碼中,Promise.all方法接受一個數組作為引數,p1,p2,p3都是Promise例項,如果不是,就會先呼叫下面講到的Promise.resolve方法,將引數轉為Promise例項.再進一步處理. (Promise.all方法的引數可以不是陣列,但必須具有Iterator介面,且返回的每個成員都是Promise例項) p的狀態由p1,p2,p3決定,分成兩種情況.

  1. 只有p1,p2,p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1,p2,p3的返回值組成一個數組,傳遞給p的回撥函式.
  2. 只要p1,p2,p3之中有一個被 rejected, p的狀態就變成rejected,此時第一個被rejected的例項的返回值,會傳遞給p的回撥函式.

下面是一個具體的例子:

//生成一個Promise物件的陣列
const promises = [2,3,5,7,11,13].map(function (id) {
	return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts){
	// ...
}).catch(function(reason){
	// ...
});

上面的程式碼中,promises是包含6個Promise例項的陣列,只有這6個例項的狀態都變成fulfilled,或者其中一個變為rejected,才會呼叫Promise.all方法後面的回撥函式.

  1. List item