Promise作用以及基本使用

林暉發表於2018-10-15

Promise的作用以及基本使用

關於Promise的概念,在實際使用之前對其的理解一直比較模糊,只是停留在一些文件上的描述。在使用中其實可以根據其特性進行一些更佳的實踐。在這裡簡單介紹一下其作用以及基礎用法。

作用


Promise物件可以理解為一次執行的非同步操作,使用promise物件之後可以使用一種鏈式呼叫的方式來組織程式碼;讓程式碼更加的直觀。也就是說,有了Promise物件,就可以將非同步操作以同步的操作的流程表達出來,避免了層層巢狀的回撥函式。總結一下就是可以將原先不可控的回撥通過promise轉為更加可控更清晰的方式表達,更加高效,更便於維護。

示例:未使用promise,回撥必須層層巢狀

$.ajax(url1, function(data1){
    // do something1...
    $.ajax(url2, function(data2){
        // do something2...
        $.ajax(url3, function(data3){
            // do something3...
        })
    });
});
複製程式碼

如果有多個巢狀,導致程式碼不夠直觀,邏輯梳理上會更加的困難。並且這幾個操作都是有執行的一個依賴關係,需要等待上一個請求完成才可以進行下一個請求,而這些本身就是非同步,這樣的等待是沒有必要的。所以promise可以幫助我們解決這一部分痛點。

瞭解一下Promise的特點

1、物件的狀態不受外界影響(Promise物件代表一個非同步操作,有三種狀態)。

  • pending(執行中)
  • Resolved(成功,又稱Fulfilled)
  • rejected(拒絕)

其中pending為初始狀態,fulfilled和rejected為結束狀態(結束狀態表示promise的生命週期已結束)。promise只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態.。

2、一旦狀態改變,就不會再變,任何時候都可以得到這個結果。

Promise物件的狀態改變,只有兩種可能:

  • 從Pending變為Resolved
  • 從Pending變為Rejected

只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果

3、Promise物件的缺點:

  • 無法取消Promise,一旦新建它就會立即執行,無法中途取消。
  • 如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。
  • 當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成。

4、promise相容性:除了IE這種古老的瀏覽器和一些低版本的安卓外,大部分的瀏覽器對於promise的相容性還是很友好的,所以我們可以在谷歌的控制檯直接測試我們的程式碼。

接下來用一些例子來看看具體的使用方法

基本用法


1、首先我們new一個Promise,將Promise例項化
2、然後在例項化的promise可以傳兩個引數,一個是成功之後的resolve,一個是失敗之後的reject
3、Promise例項生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回撥函式

var promise = function(isReady){
    return new Promise(function(resolve, reject){
        // do somthing, maybe async
        if (isReady){
          return resolve('成功執行');
        } else {
          return reject('出錯了');
        }
    });
}
 
//Promise例項生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回撥函式。
promise(true).then(function(value){
    // success,這裡是resolve的回撥函式
    console.log(value);  //hello world
}, function(err){
    // failure,這裡是reject的回撥函式
    console.log(err)
})
複製程式碼

上述程式碼是執行成功,返回成功執行,如果想測試一下失敗後的返回值,可以把promise(true).then...這裡改為 promise(false).then...在控制檯試下

鏈式操作


Promise並不只是簡化層層回撥的寫法,更重要的在於是通過傳遞狀態的方式來使回撥方式能夠及時的呼叫,因此,相比於callback,它更靈活,更簡單。下面我們來看看Promise的鏈式操作:

makePromise1()
.then(function(value){
    console.log(value);
    return makePromise2();
})
.then(function(value){
    console.log(value);
    return makePromise3();
})
.then(function(value){
    console.log(value);
});

function makePromise1(){
    var p = new Promise(function(resolve, reject){
        //非同步操作
        setTimeout(function(){
            console.log('非同步1');
            resolve('非同步1引數');
        }, 2000);
    });
    return p;            
}
function makePromise2(){
    var p = new Promise(function(resolve, reject){
        //非同步操作
        setTimeout(function(){
            console.log('非同步2');
            resolve('非同步2引數');
        }, 2000);
    });
    return p;            
}
function makePromise3(){
    var p = new Promise(function(resolve, reject){
        //非同步操作
        setTimeout(function(){
            console.log('非同步3');
            resolve('非同步3引數');
        }, 2000);
    });
    return p;            
}
複製程式碼

上面的程式碼中,有三個非同步操作,makePromise1,makePromise2,makePromise3。其中第二個和第三個依次執行,也就是上一個操作完成之後才可以進行。會相繼的列印出非同步1,非同步1引數···

Promise的catch方法


var promise = function(isReady){
    return new Promise(function(resolve, reject){
        if (isReady){
          return resolve('成功執行');
        } else {
          return reject('失敗');
        }
    });
}
promise(true)
.then(function(value){
    console.log('resolved');
    console.log(value);
    console.log(wawa); //此處的wawa未定義
})
.catch(function(error){
    console.log('rejected');
    console.log(error);
});
複製程式碼

catch 方法是 then(onFulfilled, onRejected) 方法當中 onRejected 函式的一個簡單的寫法,也就是說可以寫成 then(fn).catch(fn),相當於 then(fn).then(null, fn)使用 catch 的寫法比一般的寫法更加清晰明確,其實可以類比成try/catch,這樣,其中有報錯的地方不會阻塞執行。比如定義了一個未定義wawa,正常來說它上面的程式碼也不會執行,因為被這個報錯阻塞了,有了catch,它上面的程式碼可以正常執行下去

promise.all方法


Promise.all 可以接收一個元素為 Promise 物件的陣列作為引數,當這個陣列裡面所有的 Promise 物件都變為 resolve 時,該方法才會返回。

var p1 = new Promise(function (resolve) {
   setTimeout(function () {
       resolve("第一個promise");
   }, 3000);
});

var p2 = new Promise(function (resolve) {
   setTimeout(function () {
       resolve("第二個promise");
   }, 1000);
});

Promise.all([p1, p2]).then(function (result) {
   console.log(result); // ["第一個promise", "第二個promise"]
});
複製程式碼

上面的程式碼中,all接收一個陣列作為引數,p1,p2是並行執行的,等兩個都執行完了,才會進入到then,all會把所有的結果放到一個陣列中返回,所以我們列印出我們的結果為一個陣列。值得注意的是,雖然p2的執行順序比p1快,但是all會按照引數裡面的陣列順序來返回結果。

promise.race方法


race的意思為賽跑,因此,promise.race也是傳入一個陣列,但是與promise.all不同的是,race只返回跑的快的值,也就是說result返回比較快執行的那個。

var p1 = new Promise(function (resolve) {
   setTimeout(function () {
       console.log(1);
       resolve("第一個promise");
   }, 3000);
});

var p2 = new Promise(function (resolve) {
   setTimeout(function () {
       console.log(2);
       resolve("第二個promise");
   }, 1000);
});

Promise.race([p1, p2]).then(function (result) {
   console.log(result); 
});

// 結果:
// 2
// 第二個promise
// 1
複製程式碼

在這邊可以看到,傳的值中,只有p2的返回了,但是p1沒有停止,依然有執行。race的應用場景為,比如我們可以設定為網路請求超時。寫兩個promise,如果在一定的時間內如果成功的那個我們沒有執行到,我們就執行失敗的那個

相關文章