上一篇博客JavaScript動態載入script方式引用百度地圖API,Uncaught ReferenceError: BMap is not defined 這篇文章中我接觸到一個新的單詞:Promise,藉此來記錄一下它,引用來源:JS - Promise使用詳解--摘抄筆記 因為現在還不會 ...
上一篇博客JavaScript動態載入script方式引用百度地圖API,Uncaught ReferenceError: BMap is not defined
這篇文章中我接觸到一個新的單詞:Promise,藉此來記錄一下它,引用來源:JS - Promise使用詳解--摘抄筆記
因為現在還不會jquery,就只看了原生js部分的內容。
一、promises相關概念
promises 的概念是由 CommonJS 小組的成員在 Promises/A 規範中提出來的。1,then()方法介紹
根據 Promise/A 規範,promise 是一個對象,只需要 then 這一個方法。then 方法帶有如下三個參數:- 成功回調
- 失敗回調
- 前進回調(規範沒有要求包括前進回調的實現,但是很多都實現了)。
2,Promise對象狀態
Promise 對象代表一個非同步操作,其不受外界影響,有三種狀態:- Pending(進行中、未完成的)
- Resolved(已完成,又稱 Fulfilled)
- Rejected(已失敗)。
3,Promise/A規範圖解
4,目前支持Promises/A規範的庫
- Q:可以在NodeJS 以及瀏覽器上工作,與jQuery相容,可以通過消息傳遞遠程對象。
- RSVP.js:一個輕量級的庫,它提供了組織非同步代碼的工具。
- when.js:體積小巧,使用方便。
- NodeJS的Promise
- jQuery 1.5:據說是基於“CommonJS Promises/A”規範
- WinJS / Windows 8 / Metro
二、使用promises的優勢
1,解決回調地獄(Callback Hell)問題
(1)有時我們要進行一些相互間有依賴關係的非同步操作,比如有多個請求,後一個的請求需要上一次請求的返回結果。過去常規做法只能 callback 層層嵌套,但嵌套層數過多的話就會有 callback hell 問題。比如下麵代碼,可讀性和維護性都很差的。firstAsync(function(data){ //處理得到的 data 數據 //.... secondAsync(function(data2){ //處理得到的 data2 數據 //.... thirdAsync(function(data3){ //處理得到的 data3 數據 //.... }); }); });
(2)如果使用 promises 的話,代碼就會變得扁平且更可讀了。前面提到 then 返回了一個 promise,因此我們可以將 then 的調用不停地串連起來。其中 then 返回的 promise 裝載了由調用返回的值。
firstAsync() .then(function(data){ //處理得到的 data 數據 //.... return secondAsync(); }) .then(function(data2){ //處理得到的 data2 數據 //.... return thirdAsync(); }) .then(function(data3){ //處理得到的 data3 數據 //.... });
2,更好地進行錯誤捕獲
多重嵌套 callback 除了會造成上面講的代碼縮進問題,更可怕的是可能會造成無法捕獲異常或異常捕獲不可控。(1)比如下麵代碼我們使用 setTimeout 模擬非同步操作,在其中拋出了個異常。但由於非同步回調中,回調函數的執行棧與原函數分離開,導致外部無法抓住異常。
function fetch(callback) { setTimeout(() => { throw Error('請求失敗') }, 2000) } try { fetch(() => { console.log('請求處理') // 永遠不會執行 }) } catch (error) { console.log('觸發異常', error) // 永遠不會執行 } // 程式崩潰 // Uncaught Error: 請求失敗(2)如果使用 promises 的話,通過 reject 方法把 Promise 的狀態置為 rejected,這樣我們在 then 中就能捕捉到,然後執行“失敗”情況的回調。
function fetch(callback) { return new Promise((resolve, reject) => { setTimeout(() => { reject('請求失敗'); }, 2000) }) } fetch() .then( function(data){ console.log('請求處理'); console.log(data); }, function(reason, data){ console.log('觸發異常'); console.log(reason); } );當然我們在 catch 方法中處理 reject 回調也是可以的。
function fetch(callback) { return new Promise((resolve, reject) => { setTimeout(() => { reject('請求失敗'); }, 2000) }) } fetch() .then( function(data){ console.log('請求處理'); console.log(data); } ) .catch(function(reason){ console.log('觸發異常'); console.log(reason); });
第二部分:
JS - Promise使用詳解2(ES6中的Promise)
2015年6月, ES2015(即 ECMAScript 6、ES6) 正式發佈。其中 Promise 被列為正式規範,成為 ES6 中最重要的特性之一。
1,then()方法
簡單來講,then 方法就是把原來的回調寫法分離出來,在非同步操作執行完後,用鏈式調用的方式執行回調函數。 而 Promise 的優勢就在於這個鏈式調用。我們可以在 then 方法中繼續寫 Promise 對象並返回,然後繼續調用 then 來進行回調操作。 (1)下麵通過樣例作為演示,我們定義做飯、吃飯、洗碗(cook、eat、wash)這三個方法,它們是層層依賴的關係,下一步的的操作需要使用上一部操作的結果。(這裡使用 setTimeout 模擬非同步操作)//做飯 function cook(){ console.log('開始做飯。'); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('做飯完畢!'); resolve('雞蛋炒飯'); }, 1000); }); return p; } //吃飯 function eat(data){ console.log('開始吃飯:' + data); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('吃飯完畢!'); resolve('一塊碗和一雙筷子'); }, 2000); }); return p; } function wash(data){ console.log('開始洗碗:' + data); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('洗碗完畢!'); resolve('乾凈的碗筷'); }, 2000); }); return p; }
(2)使用 then 鏈式調用這三個方法:
cook() .then(function(data){ return eat(data); }) .then(function(data){ return wash(data); }) .then(function(data){ console.log(data); });當然上面代碼還可以簡化成如下:
cook() .then(eat) .then(wash) .then(function(data){ console.log(data); });
(3)運行結果如下:
2,reject()方法
上面樣例我們通過 resolve 方法把 Promise 的狀態置為完成態(Resolved),這時 then 方法就能捕捉到變化,並執行“成功”情況的回調。 而 reject 方法就是把 Promise 的狀態置為已失敗(Rejected),這時 then 方法執行“失敗”情況的回調(then 方法的第二參數)。 (1)下麵同樣使用一個樣例做演示//做飯 function cook(){ console.log('開始做飯。'); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('做飯失敗!'); reject('燒焦的米飯'); }, 1000); }); return p; } //吃飯 function eat(data){ console.log('開始吃飯:' + data); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('吃飯完畢!'); resolve('一塊碗和一雙筷子'); }, 2000); }); return p; } cook() .then(eat, function(data){ console.log(data + '沒法吃!'); })
運行結果如下:
(2)如果我們只要處理失敗的情況可以使用 then(null, ...),或是使用接下來要講的 catch 方法。cook() .then(null, function(data){ console.log(data + '沒法吃!'); })
3,catch()方法
(1)它可以和 then 的第二個參數一樣,用來指定 reject 的回調
cook() .then(eat) .catch(function(data){ console.log(data + '沒法吃!'); });(2)它的另一個作用是,當執行 resolve 的回調(也就是上面 then 中的第一個參數)時,如果拋出異常了(代碼出錯了),那麼也不會報錯卡死 js,而是會進到這個 catch 方法中。
//做飯 function cook(){ console.log('開始做飯。'); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('做飯完畢!'); resolve('雞蛋炒飯'); }, 1000); }); return p; } //吃飯 function eat(data){ console.log('開始吃飯:' + data); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('吃飯完畢!'); resolve('一塊碗和一雙筷子'); }, 2000); }); return p; } cook() .then(function(data){ throw new Error('米飯被打翻了!'); eat(data); }) .catch(function(data){ console.log(data); });
運行結果如下:
這種錯誤的捕獲是非常有用的,因為它能夠幫助我們在開發中識別代碼錯誤。比如,在一個 then() 方法內部的任意地方,我們做了一個 JSON.parse() 操作,如果 JSON 參數不合法那麼它就會拋出一個同步錯誤。用回調的話該錯誤就會被吞噬掉,但是用 promises 我們可以輕鬆的在 catch() 方法里處理掉該錯誤。 (3)還可以添加多個 catch,實現更加精準的異常捕獲。somePromise.then(function() { return a(); }).catch(TypeError, function(e) { //If a is defined, will end up here because //it is a type error to reference property of undefined }).catch(ReferenceError, function(e) { //Will end up here if a wasn't defined at all }).catch(function(e) { //Generic catch-the rest, error wasn't TypeError nor //ReferenceError });
4,all()方法
Promise 的 all 方法提供了並行執行非同步操作的能力,並且在所有非同步操作執行完後才執行回調。 (1)比如下麵代碼,兩個個非同步操作是並行執行的,等到它們都執行完後才會進到 then 裡面。同時 all 會把所有非同步操作的結果放進一個數組中傳給 then。//切菜 function cutUp(){ console.log('開始切菜。'); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('切菜完畢!'); resolve('切好的菜'); }, 1000); }); return p; } //燒水 function boil(){ console.log('開始燒水。'); var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('燒水完畢!'); resolve('燒好的水'); }, 1000); }); return p; } Promise .all([cutUp(), boil()]) .then(function(results){ console.log("準備工作完畢:"); console.log(results); });(2)運行結果如下:
5,race()方法
race 按字面解釋,就是賽跑的意思。race 的用法與 all 一樣,只不過 all 是等所有非同步操作都執行完畢後才執行 then 回調。而 race 的話只要有一個非同步操作執行完畢,就立刻執行 then 回調。 註意:其它沒有執行完畢的非同步操作仍然會繼續執行,而不是停止。 (1)這裡我們將上面樣例的 all 改成 racePromise .race([cutUp(), boil()]) .then(function(results){ console.log("準備工作完畢:"); console.log(results); });(2)race 使用場景很多。比如我們可以用 race 給某個非同步請求設置超時時間,並且在超時後執行相應的操作。
//請求某個圖片資源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxxxxx'; }); return p; } //延時函數,用於給請求計時 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('圖片請求超時'); }, 5000); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });
上面代碼 requestImg 函數非同步請求一張圖片,timeout 函數是一個延時 5 秒的非同步操作。我們將它們一起放在 race 中賽跑。
- 如果 5 秒內圖片請求成功那麼便進入 then 方法,執行正常的流程。
- 如果 5 秒鐘圖片還未成功返回,那麼則進入 catch,報“圖片請求超時”的信息。