ES6中的Promise物件

Gping發表於2018-07-17

什麼是Promise物件?

Promise是非同步程式設計的一種解決方案,比起傳統的解決方案(回撥函式和事件),它顯得更加的強大和方便(具體請看下文)。從語法上來講,Promise是一個物件,從它可以獲取非同步操作的訊息。Promise物件提供統一的API,各種非同步操作都可以用同樣的方法進行處理。

Promise有什麼用?

大家一致會回答,解決回撥地獄。那什麼又是回撥地獄?它有什麼缺點?

如果要執行四個步驟,依次執行,那麼傳統我們可能會這麼做:

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});
複製程式碼

為了解決這種看起來不很舒服,寫起來更不舒服的寫法,我們可能會想起鏈式寫法:

var obj = {
    stepOne: function () {
        console.log('one');
        return this;
    },
    stepTwo: function () {
        console.log('two');
        return this;
    },
    stepThree: function () {
        console.log('three');
        return this;
    }
}

obj.stepOne().stepThree().stepTwo();
複製程式碼

這種寫法的核心在於,每次都返回this。 這樣的好處在於:

  • 每一個操作都是獨立的一個函式
  • 可以組裝,也就是我們可以控制執行的流程

那如果我們還需要一些其他需求的時候呢?比如:

  • 如果上一步的結果作為下一步的輸入就更好了
  • 如果出錯我能夠捕捉就更好了
  • 如果我在函式中能夠控制流程就好了 ....

這個時候,就需要用到我們強大的Promise了

基礎用法

先來看看怎麼使用吧!直接上程式碼:

const p1 = new Promise(function(resolve, reject) {
    // 在這裡做一些邏輯處理
    // 如果非同步操作成功的時候,呼叫resolve
    if (true) {
        resolve('success');
    } else {
        // 操作失敗後我們就呼叫reject()
        reject('fail');
    }
})

p1.then((val) => {
    console.log(val); // success
}, (err) => {
    console.log(err)
})
複製程式碼

我們可以看到Promise是一個建構函式,用來生成Promise例項,它接收一個函式作為引數,這個函式有兩個引數——resolve和reject(它們也是兩個函式,已經由JavaScript引擎提供,不用自己部署)。

我們經常會在這個函式裡處理一些邏輯,如果處理成功後我們會執行resolve(將狀態從“未完成”轉為“完成”),失敗後執行reject(將狀態從“未完成”轉換為“失敗”)。

三個狀態

  • pedding(未完成)
  • resolved(完成)
  • rejected(失敗)

例項生成之後,我們可以使用then方法指定resolved和rejected狀態的回撥函式。像上面的程式碼,就相當於執行第一個回撥函式,第二個不會執行(這個時候其實不提供第二個引數也是可以的)。

如果是執行失敗,狀態變成reject的時候,就會執行第二個回撥函式,就會輸出'fail'。但是其實我們不是很推薦這樣的寫法,我們可以將第二個回撥函式寫catch方法的形式。

const p1 = new Promise(function(resolve, reject) {
    // 在這裡做一些邏輯處理
    // 如果非同步操作成功的時候,呼叫resolve
    if (false) {
        resolve('success');
    } else {
        // 操作失敗後我們就呼叫reject()
        reject('fail');
    }
})

p1.then((val) => {
    console.log(val);
}).catch((err) => {
    console.log(err); // fail
})
複製程式碼

這種寫法類似於try...catch...,更加易於我們理解。這個時候我們其實就已經解決了出錯我們能夠捕捉的問題了。

控制程式碼的執行順序

採用鏈式的then,可以指定一組按照次序呼叫的回撥函式。這個時候前面的一個返回的可能是另外一個Promise物件(也就是說有非同步操作)。這樣後面的這個Promise就依賴於前面Promise物件的狀態。

const p1 = new Promise(function(resolve, reject) {
    console.log(1);
    setTimeout(function() {
        console.log(2);
        resolve();
    }, 1000);
})

const p2 = new Promise(function(resolve, reject) {
    console.log(3);
    resolve(p1);
})

p2.then(() => {
    console.log(4);
}).then(() => {
    console.log(5);
})
複製程式碼

輸出結果

以上程式碼,我們應該注意一點p2的resolve方法將p1作為引數,也就是說p2的執行依賴於p1的執行。當p2準備好的時候,p1可能還沒準備好,這個時候p2就得等p1。

另外,需要注意的一點就是,then方法返回的是一個新的Promise例項(注意,不是之前的Promise例項),因此可以採用鏈式寫法,即then方法之後再呼叫另一個then方法。這樣,我們就可以實現了上面的需求了!

Promise的幾個重要方法

下面簡單介紹一下Promise物件的幾個方法,我們經常也會用到。

Promise.all()

直接上程式碼:

const p = Promise.all([p1, p2, p3]);
複製程式碼

Promise.all方法接收一個陣列作為引數,p1、p2、p3都是Promise例項。這個時候p的狀態由p1、p2、p3。它們的依賴機制類似於電路中的串聯,現在我們使用三條線(就是p1、p2、p3)進行串聯,p就是最後燈泡,燈泡要亮的話,三個都要成功,只要其中的一個沒有成功,那麼p的狀態就是rejected。

Promise.race()

const p = Promise.race([p1, p2, p3]);
複製程式碼

這個方法也是接受一個陣列作為引數。race的英文意思就有競賽的意思。上面的程式碼中表示,p1、p2、p3中哪個勝出(最新狀態發生改變,不管是失敗還是成功),那麼p的狀態就會跟著它改變。

ES6中的Promise物件

Promise.resolve()

作用:將現有物件轉換成Promise物件,這樣我們就可以呼叫then方法等

const jsPromise = Promise.resolve($.ajax('/whatever.json'));
複製程式碼

Promise.reject()

Promise.reject(reason)方法也會返回一個Promise例項,該例項狀態為rejected。

const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了
複製程式碼

參考: https://cnodejs.org/topic/560dbc826a1ed28204a1e7de

http://es6.ruanyifeng.com/#docs/promise

相關文章