Promise()講解
Promise對象
Promise是一個構造函數
自己身上有all、reject、resolve這幾個眼熟的方法,
原型上有then、catch等同樣很眼熟的方法。這么說用Promise new出來的對象肯定就有then、catch方法嘍,沒錯
var p = new Promise(function(resolve,reject){
? ? //做一些異步操作
? ? setTimeout(function(){
? ? ? ? console.log('執行完成');
? ? ? ? resolve('隨便什么數據');
? ? },2000);
});
Promise的構造函數接收一個參數,是函數,并且傳入兩個參數:resolve,reject,分別表示異步操作執行成功后的回調函數和異步操作執行失敗后的回調函數。其實這里用“成功”和“失敗”來描述并不準確,按照標準來講,resolve是將Promise的狀態置為fullfiled,reject是將Promise的狀態置為rejected。不過在我們開始階段可以先這么理解,后面再細究概念。
在上面的代碼中,我們執行了一個異步操作,也就是setTimeout,2秒后,輸出“執行完成”,并且調用resolve方法。
function runAsync(){
? ? var p = new Promise(function(resolve,reject){
? ? ? ? //做一些異步操作
? ? ? ? setTimeout(function(){
? ? ? ? ? ? console.log('執行完成');
? ? ? ? ? ? resolve('隨便什么數據');
? ? ? ? },2000);
? ? });
? ? return p;? ? ? ? ? ?
}
runAsync()
這時候你應該有兩個疑問:1.包裝這么一個函數有毛線用?2.resolve(‘隨便什么數據’);這是干毛的?
我們繼續來講。在我們包裝好的函數最后,會return出Promise對象,也就是說,執行這個函數我們得到了一個Promise對象。還記得Promise對象上有then、catch方法吧?這就是強大之處了,看下面的代碼:
runAsync().then(function(data){
? ? console.log(data);
? ? //后面可以用傳過來的數據做些其他操作
? ? //......
});
這時候你應該有所領悟了,原來then里面的函數就跟我們平時的回調函數一個意思,能夠在runAsync這個異步任務執行完成之后被執行。這就是Promise的作用了,簡單來講,就是能把原來的回調寫法分離出來,在異步操作執行完后,用鏈式調用的方式執行回調函數
鏈式操作的用法
所以,從表面上看,Promise只是能夠簡化層層回調的寫法,而實質上,Promise的精髓是“狀態”,用維護狀態、傳遞狀態的方式來使得回調函數能夠及時調用,它比傳遞callback函數要簡單、靈活的多。所以使用Promise的正確場景是這樣的:
runAsync1()
.then(function(data){
? ? console.log(data);
? ? return runAsync2();
})
.then(function(data){
? ? console.log(data);
? ? return runAsync3();
})
.then(function(data){
? ? console.log(data);
});
reject的用法
到這里,你應該對“Promise是什么玩意”有了最基本的了解。那么我們接著來看看ES6的Promise還有哪些功能。我們光用了resolve,還沒用reject呢,它是做什么的呢?事實上,我們前面的例子都是只有“執行成功”的回調,還沒有“失敗”的情況,reject的作用就是把Promise的狀態置為rejected,這樣我們在then中就能捕捉到,然后執行“失敗”情況的回調??聪旅娴拇a。
function getNumber(){
? ? 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;? ? ? ? ? ?
}
getNumber()
.then(
? ? function(data){
? ? ? ? console.log('resolved');
? ? ? ? console.log(data);
? ? },?
? ? function(reason,data){
? ? ? ? console.log('rejected');
? ? ? ? console.log(reason);
? ? }
);
catch的用法
我們知道Promise對象除了then方法,還有一個catch方法,它是做什么用的呢?其實它和then的第二個參數一樣,用來指定reject的回調,用法是這樣:
getNumber()
.then(function(data){
? ? console.log('resolved');
? ? console.log(data);
})
.catch(function(reason){
? ? console.log('rejected');
? ? console.log(reason);
});
也就是說進到catch方法里面去了,而且把錯誤原因傳到了reason參數中。即便是有錯誤的代碼也不會報錯了,這與我們的try/catch語句有相同的功能。
all的用法
Promise的all方法提供了并行執行異步操作的能力,并且在所有異步操作執行完后才執行回調。我們仍舊使用上面定義好的runAsync1、runAsync2、runAsync3這三個函數,看下面的例子:
Promise
.all([runAsync1(),runAsync2(),runAsync3()])
.then(function(results){
? ? console.log(results);
});
用Promise.all來執行,all接收一個數組參數,里面的值最終都算返回Promise對象。這樣,三個異步操作的并行執行的,等到它們都執行完后才會進到then里面。那么,三個異步操作返回的數據哪里去了呢?都在then里面呢,all會把所有異步操作的結果放進一個數組中傳給then,就是上面的results
有了all,你就可以并行執行多個異步操作,并且在一個回調中處理所有的返回數據,是不是很酷?有一個場景是很適合用這個的,一些游戲類的素材比較多的應用,打開網頁時,預先加載需要用到的各種資源如圖片、flash以及各種靜態文件。所有的都加載完后,我們再進行頁面的初始化。
race的用法
all方法的效果實際上是「誰跑的慢,以誰為準執行回調」,那么相對的就有另一個方法「誰跑的快,以誰為準執行回調」,這就是race方法,這個詞本來就是賽跑的意思。race的用法與all一樣,我們把上面runAsync1的延時改為1秒來看一下
Promise
.race([runAsync1(),runAsync2(),runAsync3()])
.then(function(results){
? ? console.log(results);
});
這三個異步操作同樣是并行執行的。結果你應該可以猜到,1秒后runAsync1已經執行完了,此時then里面的就執行了。結果是這樣的:?
這個race有什么用呢?
requestImg函數會異步請求一張圖片,我把地址寫為”xxxxxx”,所以肯定是無法成功請求到的。timeout函數是一個延時5秒的異步操作。我們把這兩個返回Promise對象的函數放進race,于是他倆就會賽跑,如果5秒之內圖片請求成功了,那么遍進入then方法,執行正常的流程。如果5秒鐘圖片還未成功返回,那么timeout就跑贏了,則進入catch,報出“圖片請求超時”的信息