ES6 Promise物件的用法
ES6規定,Promise物件是一個建構函式,用來生成Promise例項
new 一個Promise物件
var p = new Promise(function(resolve,reject){
//做一些非同步操作
setTimeout(function(){
console.log('執行完成');
resolve('成功輸出');
},2000);
});
Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve和reject。它們是兩個函式,有JavaScript引擎提供,不用自己部署
resolve函式的作用 : 將Promise物件的狀態從'未完成'變為'成功',在非同步操作成功時呼叫,並將非同步操作的結果作為引數傳遞出去
reject函式的作用:將Promise物件的狀態從'未完成'變為'失敗',在非同步操作失敗時呼叫,並將非同步操報出的錯誤作為引數傳遞出去
resolve是將Promise的狀態置為fullfiled,reject是將Promise的狀態置為rejected
在上面的程式碼中,我們執行了一個非同步操作,也就是setTimeout,2秒後,輸出“執行完成”,並且呼叫resolve方法。
執行程式碼,會在2秒後輸出“執行完成”。但是我只是new了一個物件,並沒有呼叫它,我們傳進去的函式就已經執行了,這是需要注意的一個細節。所以我們用Promise的時候一般是包在一個函式中,在需要的時候去執行這個函式
function timeout() {
var p = new Promise(function(resolve,reject){
//做一些非同步操作
setTimeout(function(){
console.log('執行完成');
resolve('成功輸出');
},2000);
});
return p
}
timeout()
所以包裝這麼一個函式有什麼用?resolve('成功輸出')是有什麼用?
觀察函式,我們發現 , 在我們包裝好的函式最後,會return出Promise物件,也就是說,執行這個函式我們得到了一個Promise物件。知道Promise物件上有then、catch方法吧?這就是強大之處了,看下面的程式碼:
timeout().then(function(data){
console.log(data);
//後面可以用傳過來的資料做些其他操作
})
在timeout()的返回上直接呼叫then方法,then接收一個引數,是函式,並且會拿到我們在timeout中呼叫resolve時傳的的引數。執行這段程式碼,會在2秒後輸出“執行完成”,緊接著輸出“成功輸出”。
在這裡看來,then裡面的函式就和以前我們所理解的回撥函式一個意思,但是,如果有多層回撥呢?看下面程式碼
setTimeout(function(){
timeout(function(){
setTimeout(function(){
timeout(function(){
setTimeout(function(){
timeout();
},2000);
});
}, 2000);
});
}, 2000);
以上程式碼就是傳說中的回撥地獄,如果有多層業務邏輯巢狀的話,不僅會使程式碼閱讀困難,而且後面維護起來也是難點
看看Promise怎麼用
timeout().then(function(data){
console.log(data);
return timeout2();
//後面可以用傳過來的資料做些其他操作
}).then(function(data){
console.log(data);
return timeout3();
}).then(function(data){
console.log(data);
})
這樣能夠按順序,每隔兩秒輸出每個非同步回撥中的內容,在timeout2中傳給resolve的資料,能在接下來的then方法中拿到。執行結果如下:
function timeout() {
var p = new Promise(function(resolve,reject){
//做一些非同步操作
setTimeout(function(){
console.log('執行完成');
resolve('成功輸出');
},2000);
});
return p
}
function timeout2() {
var p = new Promise(function(resolve,reject){
//做一些非同步操作
setTimeout(function(){
console.log('執行完成2');
resolve('成功輸出2');
},2000);
});
return p
}
function timeout3() {
var p = new Promise(function(resolve,reject){
//做一些非同步操作
setTimeout(function(){
console.log('執行完成3');
resolve('成功輸出3');
},2000);
});
return p
}
reject的用法
將Promise物件的狀態從'未完成'變為'成功',在非同步操作成功時呼叫,並將非同步操作的結果作為引數傳遞出去
舉個栗子:
function getNum(){
var p = new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的隨機數
if(num>5){
resolve(num)
}else{
reject('數字不對哦')
}
},2000)
})
return p;
}
getNum().then(function(data){
console.log('resolved');
console.log(data)
},function(data){
console.log('rejected')
console.log(data);
})
getNum函式用來非同步獲取一個數字,2秒後執行完成,如果數字大於5,我們認為是“成功”了,呼叫resolve修改Promise的狀態。否則我們認為是“失敗”了,呼叫reject並傳遞一個引數,作為失敗的原因。
then方法可以接受兩個引數,第一個對應resolve的回撥,第二個對應reject的回撥。所以我們能夠分別拿到他們傳過來的資料。多次執行這段程式碼,會得到不同的狀態
catch的用法
catch方法是then(null,rejection)的別名,用於指定發生錯誤時的回撥函式,所以上面也可以這麼寫
getNum().then(function(data){
console.log('resolved');
console.log(data)
}).catch(function(data){
console.log('rejected')
console.log(data);
})
執行結果是一樣。不過它還有另外一個作用:在執行resolve的回撥(也就是上面then中的第一個引數)時,如果丟擲異常了(程式碼出錯了),那麼並不會報錯卡死js,而是會進到這個catch方法中。請看下面的程式碼:
getNum().then(function(data){
console.log('resolved');
console.log(data);
console.log(sss); //未定義
}).catch(function(data){
console.log('rejected')
console.log(data);
})
在resolve的回撥中,我們console.log(somedata);而somedata這個變數是沒有被定義的。如果我們不用Promise,程式碼執行到這裡就直接在控制檯報錯了
也就是說進到catch方法裡面去了,而且把錯誤原因傳到了reason引數中。即便是有錯誤的程式碼也不會報錯了,這與我們的try/catch語句有相同的功能。
all的用法
Promise.all方法用於將多個Promise例項包裝成一個新的Promise例項
Promise.all([timeout(),timeout2(),timeout3()]).then(function(results){
console.log(results);
})
用Promise.all來執行,all接收一個數組引數,裡面的值最終都算返回Promise物件。這樣,三個非同步操作的並行執行的,等到它們都執行完後才會進到then裡面。那麼,三個非同步操作返回的資料哪裡去了呢?都在then裡面呢,all會把所有非同步操作的結果放進一個數組中傳給then,就是上面的results。
race的用法
「誰跑的快,以誰為準執行回撥」,這就是race方法,race的用法與all一樣,我們把上面timeout2的延時改為1秒來看一下
Promise.race([timeout(),timeout2(),timeout3()]).then(function(results){
console.log(results);
})
我們可以用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函式會非同步請求一張圖片,我把地址寫為"xxxxxx",所以肯定是無法成功請求到的。timeout函式是一個延時5秒的非同步操作。我們把這兩個返回Promise物件的函式放進race,於是他倆就會賽跑,如果5秒之內圖片請求成功了,那麼遍進入then方法,執行正常的流程。如果5秒鐘圖片還未成功返回,那麼timeout就跑贏了,則進入catch,報出“圖片請求超時”的資訊。執行結果如下: