What is Promise? Promise是一個構造函數,接受一個參數(Function),並且該參數接受兩個參數resolve和reject(分別表示非同步操作執行成功後的回調函數、執行失敗後的回調函數) 運行代碼,2秒後輸出“執行完成”。註意,這裡只是new了一個對象,並沒有調用它,所以我們 ...
What is Promise?
Promise是一個構造函數,接受一個參數(Function),並且該參數接受兩個參數resolve和reject(分別表示非同步操作執行成功後的回調函數、執行失敗後的回調函數)
var p = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('執行完成'); resolve('成功了!'); }, 2000); });
運行代碼,2秒後輸出“執行完成”。註意,這裡只是new了一個對象,並沒有調用它,所以我們用Promise時是包在一個函數中的,如下:
function runAsync(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('執行完成'); resolve('成功了!'); }, 2000); }); return p; } runAsync();
Pormise的優勢:
1. 解決回調函數層層嵌套的問題:
(1) 有時我們需要進行一些有依賴關係的非同步操作,比如有多個請求,後一個請求需要上一次請求的返回結果,過去常規的做法是使用callback層層嵌套,這樣的代碼可讀性和維護性都很差,比如:
firstAsync(function(data){ //處理得到的 data 數據 //.... secondAsync(function(data2){ //處理得到的 data2 數據 //.... thirdAsync(function(data3){ //處理得到的 data3 數據 //.... }); }); });
(2) 使用Promise的話,代碼就會變得扁平化,可讀性更高了。比如:
firstAsync() .then(function(data){ //處理得到的 data 數據 //.... return secondAsync(); }) .then(function(data2){ //處理得到的 data2 數據 //.... return thirdAsync(); }) .then(function(data3){ //處理得到的 data3 數據 //.... });
2. 更好的進行錯誤捕捉:
(1) 使用callback嵌套,可能會造成無法捕捉異常、異常捕捉不可控等問題。比如:
function fetch(callback) { setTimeout(() => { throw Error('請求失敗') }, 2000) } try { fetch(() => { console.log('請求處理') // 永遠不會執行 }) } catch (error) { console.log('觸發異常', error) // 永遠不會執行 } // 程式崩潰 // Uncaught Error: 請求失敗
(2) 使用Promise的話,通過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); });
在上面的代碼中我們用到了Promise的then、catch方法,下麵我們再來介紹一下Promise中常用的一些方法。
then方法:
then方法就是將原來使用callback回調的寫法分離出來,在非同步操作完成之後,使用鏈式調用的方法執行回調函數。
then方法包含三個參數:
- 成功回調
- 失敗回調
- 前進回調(規範沒有要求實現前進回調)
返回一個Promise對象,也可以直接返回一個數據。比如:
function runAsync1(){ var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('非同步任務1執行完成'); resolve('成功了1'); }, 1000); }); return p; } function runAsync2(){ var p = new Promise(function(resolve, reject){ //做一些非同步操作 setTimeout(function(){ console.log('非同步任務2執行完成'); resolve('成功了2'); }, 2000); }); return p; } runAsync1() .then(function(data){ console.log(data); return runAsync2(); }) .then(function(data){ console.log(data); return '直接返回數據'; //這裡直接返回數據 }) .then(function(data){ console.log(data); }); /* 輸出: 非同步任務1執行完成 成功了1 非同步任務2執行完成 成功了2 直接返回數據 */View Code
resolve方法:
該方法把Promise的狀態置為完成態(Resolved),這是then方法就能捕捉到變化,並執行“成功”回調的方法。比如:
//做飯 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; }
使用then鏈式調用:
cook() .then(function(data){ return eat(data); }) .then(function(data){ console.log(data); }); //可以簡寫 cook() .then(eat) .then(function(data){ console.log(data); }); /* 輸出: 開始做飯。 做飯完畢! 開始吃飯:雞蛋炒飯 吃飯完畢 完成! */
reject方法:
該方法把Promise的狀態置為已失敗(rejected),then方法捕捉到變化,並執行“失敗”回調的方法。比如:
//做飯 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 + '沒法吃!'); }) /* 輸出: 開始做飯。 做飯失敗! 燒焦的米飯沒法吃! */
catch方法:
1. 和then方法的第二個參數reject方法用法一致,執行“失敗”回調方法
cook() .then(eat) .catch(function(data){ console.log(data + '沒法吃!'); });
2. 當執行第一個參數resolve方法時,如果拋出了異常(代碼錯誤),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); }); /* 輸出: 開始做飯。 做飯完畢! Error:米飯被打翻了! at:xxx.html */還可以添加多個 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 });
all方法:
該方法提供了並行執行非同步操作的能力,在所有非同步操作執行完畢之後才會進入到then方法中執行回調方法。
all方法接受一個數組,裡面的值最終都返回一個Promise對象。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); }); /* 輸出: 開始切菜。
開始燒水。 切菜完畢! 燒水完畢! 準備工作完畢: ["切好的菜","燒好的水"] */
race方法:
race按字面意思是,競賽/賽跑。與all不同的是,所有的非同步操作中只要有一個非同步操作完成,就立即執行then回調方法。比如:
Promise .race([cutUp(), boil()]) .then(function(results){ console.log("準備工作完畢:"); console.log(results); }); /* 輸出: 開始切菜。 開始燒水。 切菜完畢! 準備工作完畢: 切好的菜 燒水完畢! */
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,報“圖片請求超時”的信息。