JavaScript的Promise物件
1.Promise物件
Promise是非同步程式設計的一種解決方案,ES6原生提供了Promise物件。Promise物件代表了未來將要發生的事件,用來傳遞非同步操作的訊息。
Promise物件有兩個特點:
(1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
有了Promise物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise物件提供統一的介面,使得控制非同步操作更加容易。
Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
2.Promise的基本用法
ES6 規定,Promise物件是一個建構函式,用來生成Promise例項。
建立一個promise物件,可以使用new來呼叫Promise的構造器來進行例項化:
var promise = new Promise(function(resolve, reject) { // 非同步處理 if(/* 非同步操作成功 */){ resolve(value); } else { reject(error); } });
Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve和reject。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。
resolve函式的作用是,將Promise物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject函式的作用是,將Promise物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。
Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式。
//value和error分別是上面呼叫resolve(...)和reject(...)方法傳入的值 promise.then(function(value) { // success }, function(error) { // failure });
下面是一個Promise物件的例子
var promise = new Promise(function(resolve, reject) { console.log("1") setTimeout(function(){ console.log("2") resolve() },1000) }); console.log("3") promise.then(function(){ console.log("4") }) console.log("5")
在上述程式碼中,控制檯列印的順序是1、3、5、2、4
因為Promise新建後就會立即執行,所以會首先列印1,然後程式碼遇到非同步程式碼會先將其新增到任務佇列,等同步任務執行完畢之後才執行非同步程式碼,所以之後會先依次列印3、5。setTimeout函式一秒後在控制檯列印2,並將Promise例項的狀態變為resolved,然後觸發then方法繫結的回撥函式,列印4。
下面是一個用Promise物件實現Ajax操作的例子
function ajax(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { let data = JSON.parse(req.responseText); resolve(data['hitokoto']); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var URL = "https://v1.hitokoto.cn/?c=k"; ajax(URL).then(function onFulfilled(value){ document.write( value); }).catch(function onRejected(error){ document.write('錯誤:' + error); });
上面程式碼中,resolve 方法和 reject 方法呼叫時,都帶有引數。它們的引數會被傳遞給回撥函式。reject 方法的引數通常是 Error 物件的例項,而 resolve 方法的引數除了正常的值以外,還可能是另一個 Promise 例項,比如像下面這樣。
var p1 = new Promise(function(resolve, reject){ // ... some code }); var p2 = new Promise(function(resolve, reject){ // ... some code resolve(p1); })
上面程式碼中,p1和p2都是 Promise 的例項,但是p2的resolve方法將p1作為引數,即一個非同步操作的結果是返回另一個非同步操作。
注意,這時p1的狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態。如果p1的狀態是pending,那麼p2的回撥函式就會等待p1的狀態改變;如果p1的狀態已經是resolved或者rejected,那麼p2的回撥函式將會立刻執行。
一般來說,呼叫resolve或reject以後,Promise 的使命就完成了,後繼操作應該放到then方法裡面,而不應該直接寫在resolve或reject的後面。所以,最好在它們前面加上return語句,這樣就不會有意外。
new Promise((resolve, reject) => { return resolve(1); // 後面的語句不會執行 console.log(2); })