你可能已經知道Promises現在已經是JavaScript標準的一部分了。Chrome 32 beta版本已經實現了基本的Promise API。如今,Promise的概念在web開發中已經不是什麼新鮮玩意了。我們中的大多數人已經在一些流行的JS庫例如Q、when、RSVP.js中使用過了Promises。即使是jQuery中也有一個和Promises很類似叫做Deferred的東西。但是JavaScript原生支援Promises確實是一件很令人激動高的事情。本文將首先講述Promises的基礎知識並向你展示怎樣在你的JS開發中利用好Promises。
注意:目前Promises只是一個實驗的特性,目前只有Chrome 32 beta和最新版本的Firefox支援。 Promises綜述
一個Promise對象代表一個目前還不可用,但是在未來的某個時間點可以被解析的值。它允許你以一種同步的方式編寫非同步代碼。例如,如果你想要使用Promise API非同步呼叫一個遠端伺服器,你需要建立一個代表資料將會在未來由web服務返回的Promise對象。唯一的問題是目前資料還不可用。當請求完成並從伺服器返回時資料將變為可用資料。在此期間,Promise對象將扮演一個真實資料的代理角色。接下來,你可以在Promise對象上綁定一個回呼函數,一旦真實資料變得可用這個回呼函數將會被調用。 Promise的API
正式開始編寫代碼之前,我們先要用下面的代碼建立一個Promise對象:
if(window.Promise){//檢查瀏覽器是否支援Promise var promise = new Promise(function(resolve,reject){ //在此處編寫非同步代碼 });}
我們執行個體化了一個Promise對象並給它傳遞了一個回呼函數。這個回呼函數接收兩個參數,resolve()和reject(),它們都是函數。你所有的代碼都將放在回呼函數中,如果一切順利的話,Promise將會調用resolve(),變為fulfilled狀態。萬一發生了錯誤,reject()將會連同一個Error對象被調用,這預示著Promise變為rejected狀態。
現在我們編寫一個簡單的例子來展示Promise的用法。下面的代碼對web服務進行了一次非同步請求,它將隨機返回一個JSON格式的笑話。我們來看看Promise在此處是怎麼使用的:
if(window.Promise){ console.log('Promise found'); var promise = new Promise(function(resolve,reject){ var request = new XMLHttpRequest(); request.open('GET','http://api.icndb.com/jokes/random'); request.onload = fucntion(){ if(request.status == 200){ resolve(request.response); //我們在此處獲得了資料,因此解析Promise }else{ reject(Error(request.statusText));//狀態代碼不是200,因此調用reject } }; request.onerror = function(){ reject(Error('Error fetching data'));//錯誤發生,拒絕Promise }; request.send(); //發送請求 }); console.log('Asynchronous request made.'); promise.then(function(data){ console.log('Got data! Promise fulfilled.'); document.getElementByTagName('body')[0].textContent = JSON.parse(data).value.joke; },function(error){ console.log('Promise rejected'); console.log(error.message); });} else { console.log('Promise not available');}
在上面的代碼中,Promise()建構函式中的回呼函數包含了一段用來從遠端伺服器擷取資料的非同步代碼。在這裡,我們僅僅建立了一個AJAX請求從http://api.icndb.com/jokes/random隨機返回一個笑話。當接收到遠端伺服器返回的JSON回應是,它將傳遞給resolve()。萬一發生了錯誤,reject()就會連同一個Error對象一起被調用。
當我們執行個體化了一個Promise對象時,我們得到了一個代理,它表示資料將在未來的某個時間點可以被使用。在上面的例子中,我們期待一些資料將在未來的某個時間點從遠端伺服器返回。因此,我們怎噩夢知道資料什麼時候是可用的呢。這就是為什麼要使用Promise.then()的原因。函數then()接收兩個參數:一個成功回調和一個失敗回調。這些回呼函數在Promise處於settled狀態(fulfilled或者rejected)時被調用。如果Promise處於fulfilled狀態,成功回呼函數將會連同你傳遞給resolve()的資料一起被觸發。如果Promise處於rejected狀態,失敗回呼函數將會被調用。無論你給reject()函數傳遞了什麼參數都會傳遞給這個回呼函數。
Promise有三種狀態: pending(沒有fulfilled或者rejected) fulfilled rejected
Promise.status屬性,是代碼不可以擷取的私人屬性,用來表示以上這些創帶。一旦一個Promise變為rejected或者fulfilled,狀態將會永久的與之關聯。這意味著一個Promise只能成功或者失敗一次。如果Promise已經處於fulfilled狀態瞭然後你在它上面連同兩個回呼函數綁定了then()方法,那麼成功回調將會被正確的調用。因此,在Promise的世界中,我們並沒有興趣知道Promise什麼時候完成。我們只關心Promise的最終輸出結果。 鏈式Promises
有些時候,我們想要將Promises鏈式組裝起來。例如,你可能會有多個回調操作。當一個操作返回給你一個資料是,你將會基於這個資料開始另一個操作,以此類推。下面的代碼將會展示怎樣進行鏈式Promises操作:
function getPromise(url){ //返回一個Promise //發送一個非同步請求到url作為Promise的一部分 //在得到結果之後,連同資料解析promise} var promise = getPromise('some url here'); promise.then(function(result){ //我們在這裡得到結果 return getPromise(result); //在此處再次返回一個Promise }).then(function(result){ //處理最終結果});
該部分的技巧在於當你在then()中返回了一個簡單的值是,下一個then()將會連同這個傳回值被調用。但是如果你在then()中返回了一個Promise,下一個then()將會等到這個Promise完成時才會被調用。 處理錯誤
以現在已經知道了then()函數接收兩個回呼函數作為參數。第二個回呼函數在Promise變為rejected時被掉哦那個。但是,我們還有一個catch()函數來處理Promise的rejected狀態。我們來看看下面的代碼:
promise.then(fucntion(result){ console.log('Got data!',relust);}).catch(function(error){ console.log('Error occurred!',error);});
它等價於:
promise.then(function(result){ consoe.log('Got data',result);},function(error){console.log('Error occurred!',error);});
需要注意的是如果Promise處於rejected狀態並且then()沒有一個失敗回呼函數,那麼控制將會移動到下一個擁有失敗回呼函數的then()或者下一個catch()。除了顯式的處理Promise的rejected狀態,當Promise()構造器函數回調拋出一個錯誤時catch()也會被調用。因此,你依然可以使用catch()來實現日誌的作用。注意到我們會使用try…catch來處理錯誤,但是在Promise中因為有了catch()我們沒有必要使用這個方法,無論是同步還是非同步錯誤。 總結
在本文匯總我們簡要的介紹了JavaScript中新的Promise API。顯然它使我們編寫代碼更加輕鬆了。我們可以在不知道非同步代碼將來的傳回值時前進。Promise的API還包含很多東西,我們在後面將會一一講述。