JavaScript之Promise對象

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。前端

Promise 對象是一個代理對象(代理一個值),被代理的值在 Promise 對象建立時多是未知的。它容許你爲異步操做的成功和失敗分別綁定相應的處理方法(handlers)。 這讓異步方法能夠像同步方法那樣返回值,但並非當即返回最終執行結果,而是一個能表明將來出現的結果的 Promise 對象。 Promise 對象有如下兩個特色:ajax

對象的狀態不受外界影響。Promise 對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功) 和 rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。 Promise 對象的狀態改變,只有兩種可能:從 pending 變爲 fulfilled 和 從 pending 變爲 rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對 Promise 對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。 基本用法編程

new Promise( function(resolve, reject) {...} /* executor */  );
  • Promise 對象的初始化接收一個執行函數 executor,executor 是帶有 resolve 和 reject 兩個參數的函數 。數組

  • Promise 構造函數執行時會當即調用 executor 函數, resolve 和 reject 兩個函數做爲參數傳遞給 executor(executor 函數在 Promise 構造函數返回新建對象前被調用)。promise

  • resolve 和 reject 函數被調用時,分別將 promise 的狀態改成 fulfilled(完成) 或 rejected(失敗)。executor 內部一般會執行一些異步操做,一旦完成,能夠調用 resolve 函數來將 promise 狀態改爲 fulfilled,或者在發生錯誤時將它的狀態改成 rejected。異步

若是在 executor 函數中拋出一個錯誤,那麼該 promise 狀態爲 rejected。executor函數的返回值被忽略。異步編程

先看個示例:(注:後文的示例均使用 setTimeout 模擬異步操做)函數

// 從 pending 變爲 fulfilled
var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');
        resolve('promise fulfilled!');
    }, 500);
}).then(function(data) {
    console.log(data);
});
// Hi,
// promise fulfilled!

// 從 pending 變爲 rejected
var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');
        reject('promise rejected!');
    }, 500);
}).then(null, function(error) {
    console.log(error); //歡迎加入全棧開發交流圈一塊兒學習交流:864305860 
});                              //面向1-3年前端人員
// Hi,                           //幫助突破技術瓶頸,提高思惟能力  
// promise rejected!

從 pending 變爲 fulfilled 這段代碼,當執行 new Promise() 時,傳入的執行函數就當即執行了,此時其內部有一個異步操做(過 500ms 以後執行),等過了 500ms 以後先執行 console.log('Hi,'); 輸出 Hi,,此時 promise 的狀態爲 pending(進行中),而執行 resolve('Promise!'); 則修改 promise 的狀態爲 fulfilled(完成),而後咱們調用 then() 接收 promise 在 fulfilled 狀態下傳遞的值,此時輸出 'Promise!'。學習

同理,從 pending 變爲 rejected 這段代碼基本差很少,不一樣的是異步操做調用了 reject 方法,then 方法使用第二個參數接收 rejected 狀態下傳遞的值。url

Promise.prototype.then()

then 的做用是爲 Promise 實例添加狀態改變時的回調函數。

then 方法的第一個參數是 resolved 狀態的回調函數,第二個參數(可選)是 rejected 狀態的回調函數。

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態爲200表明成功,不是200表明失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}, function(error) {                    
    console.log(error);           
});                                      
// 若是調用 resolve 方法,輸出以下:
// Hi,
// promise fulfilled!

// 若是調用 reject 方法,輸出以下:
// Hi,
// promise rejected!

then 方法返回的是一個新的 Promise 實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即 then 方法後面再調用另外一個 then 方法。採用鏈式的 then,能夠指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的仍是一個 Promise 對象(即有異步操做),這時後一個回調函數,就會等待該 Promise 對象的狀態發生變化,纔會被調用。

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');
        resolve();
    }, 500);
}).then(function() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            // 模擬請求,請求狀態爲200表明成功,不是200表明失敗
            if (status === 200) {
                resolve('promise fulfilled!');
            } else {
                reject('promise rejected!');
            }
        });
    })
}).then(function(data) {
    console.log(data); //歡迎加入全棧開發交流圈一塊兒學習交流:864305860 
}, function(error) {       //面向1-3年前端人員
    console.log(error);  //幫助突破技術瓶頸,提高思惟能力  
});
// 若是第一個 then 調用 resolve 方法,第二個 then 調用第一個回調函數,最終輸出以下:
// Hi,
// promise fulfilled!

// 若是第一個 then 調用 reject 方法,第二個 then 調用第一個回調函數,最終輸出以下:
// Hi,
// promise rejected!
Promise.prototype.catch()

catch 方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

因此下面代碼:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態爲200表明成功,不是200表明失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}, function(error) {
    console.log(error);
});

等價於:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態爲200表明成功,不是200表明失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}).catch(function(error) {
    console.log(error);
});

若是沒有使用 catch 方法或者 then 第二個參數指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應,這跟傳統的 try/catch 代碼塊是不一樣。

catch 方法返回的仍是一個 Promise 對象,所以後面還能夠接着調用 then 方法。

catch 方法與 .then(null, rejection) 的不一樣:

若是異步操做拋出錯誤,狀態就會變爲 rejected,就會調用 catch 方法指定的回調函數,處理這個錯誤。 then 方法指定的回調函數,若是運行中拋出錯誤,也會被 catch 方法捕獲。 catch 方法的寫法更接近同步的寫法(try/catch)。 所以,建議老是使用 catch 方法,而不使用 then 方法的第二個參數。

Promise.prototype.finally()

finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態爲200表明成功,不是200表明失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}).catch(function(error) {
    console.log(error);
}).finally(function() {
    console.log('I am finally!');
});

上面代碼中,無論 promise 最後的狀態,在執行完 then 或 catch 指定的回調函數之後,都會執行 finally 方法指定的回調函數。

Promise.all()

Promise.all 方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

var p = Promise.all([p1, p2]);

上面代碼中,Promise.all 方法接受一個數組做爲參數,p一、p2 都是 Promise 實例,若是不是,就會先調用下面講到的 Promise.resolve 方法,將參數轉爲 Promise 實例,再進一步處理。(Promise.all方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。)

p的狀態由p一、p2決定,分紅兩種狀況。

  • 只有 p一、p2 的狀態都變成 fulfilled,p 的狀態纔會變成 fulfilled,此時 p一、p2 的返回值組成一個數組,傳遞給 p 的回調函數。

  • 只要 p一、p2 之中有一個被 rejected,p 的狀態就變成 rejected,此時第一個被 reject 的實例的返回值,會傳遞給 p 的回調函數。

示例:

試想一個頁面聊天系統,咱們須要從兩個不一樣的 URL 分別得到用戶的我的信息和好友列表,這兩個任務是能夠並行執行的,用Promise.all()實現。

// 並行執行異步任務
var p1 = new Promise(function (resolve, reject) {
    setTimeout(function() {
        // 模擬請求,請求狀態爲200表明成功,不是200表明失敗
        if (status === 200) {
            resolve('P1');
        } else {
            reject('error');
        }
    }, 500);
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同時執行p1和p2,並在它們都完成後執行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 輸出:['P1', 'P2']
}).catch(function(error) {
    console.log(error); // 若是p1執行失敗,則輸出:error
});

注意,若是做爲參數的 Promise 實例,本身定義了 catch 方法,那麼它一旦被 rejected,並不會觸發 Promise.all() 的 catch 方法。

Promise.race()

Promise.race 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

var p = Promise.race([p1, p2]);

上面代碼中,只要 p一、p2 之中有一個實例率先改變狀態,p 的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給 p 的回調函數。

Promise.race 方法的參數與 Promise.all 方法同樣,若是不是 Promise 實例,就會先調用下面講到的 Promise.resolve 方法,將參數轉爲 Promise 實例,再進一步處理。

示例:

有些時候,多個異步任務是爲了容錯。好比,同時向兩個 URL 讀取用戶的我的信息,只須要得到先返回的結果便可。這種狀況下,用Promise.race()實現。

// 多任務容錯
var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 400, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P2'
});
Promise.resolve()

有時須要將現有對象轉爲 Promise 對象,Promise.resolve 方法就起到這個做用。

Promise.resolve方法的參數分紅四種狀況:

(1)參數是一個 Promise 實例

若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。

(2)參數是一個 thenable 對象

thenable 對象指的是具備 then 方法的對象,好比下面這個對象。

var thenable = {
    then: function (resolve, reject) {
        resolve(42);
    }
};

Promise.resolve 方法會將這個對象轉爲 Promise 對象,而後就當即執行 thenable 對象的 then 方法。

var thenable = {
    then: function (resolve, reject) {
        resolve(42);
    }
};

var p1 = Promise.resolve(thenable);
p1.then(function (value) {
    console.log(value);  // 42
});

上面代碼中,thenable 對象的 then 方法執行後,對象 p1 的狀態就變爲 resolved,從而當即執行最後那個 then 方法指定的回調函數,輸出 42。

(3)參數不是具備 then 方法的對象,或根本就不是對象

若是參數是一個原始值,或者是一個不具備 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態爲 resolved。

var p = Promise.resolve('Hello');

p.then(function (s) {
    console.log(s)
});
// 'Hello'

var p1 = Promise.resolve(true);

p1.then(function (b) {
    console.log(b)
});
// true

var p2 = Promise.resolve(1);

p1.then(function (n) {
    console.log(n)
});
// 1

(4)不帶有任何參數

Promise.resolve 方法容許調用時不帶參數,直接返回一個 resolved 狀態的 Promise 對象。

因此,若是但願獲得一個 Promise 對象,比較方便的方法就是直接調用 Promise.resolve 方法。

Promise.reject() Promise.reject 方法也會返回一個新的 Promise 實例,該實例的狀態爲 rejected。

注意,Promise.reject 方法的參數,會原封不動地做爲 reject 的參數,變成後續方法的參數。這一點與 Promise.resolve 方法不一致。

var thenable = {
    then(resolve, reject) {
        reject('出錯了');
    }
};

Promise.reject(thenable)
    .catch(e = > {
    console.log(e === thenable)
})
// true

上面代碼中,Promise.reject 方法的參數是一個 thenable 對象,執行之後,後面 catch 方法的參數不是 reject 拋出的 出錯了 這個字符串,而是 thenable 對象。

加載圖片

咱們能夠將圖片的加載寫成一個 Promise,一旦加載完成,Promise 的狀態就發生變化。

function (path) {
    return new Promise(function (resolve, reject) {
        const image = new Image();
        image.onload = resolve;
        image.onerror = reject;
        image.src = path;
    });
};

封裝ajax

咱們能夠將 ajax 請求寫成一個 Promise,根據請求的不一樣狀態改變 Promise 的狀態。

function ajax(method, url, data) {
    var request = new XMLHttpRequest();
    return new Promise(function (resolve, reject) {
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    resolve(request.responseText);
                } else {
                    reject(request.status);
                } //歡迎加入全棧開發交流圈一塊兒學習交流:864305860 
            }    //面向1-3年前端人員
        };      //幫助突破技術瓶頸,提高思惟能力  
        request.open(method, url);
        request.send(data);
    });
}

總結 優勢:

能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數(回調地獄)。 在異步執行的流程中,能夠把執行代碼和處理結果的代碼清晰地分離開來。

缺點:

沒法取消 Promise,一旦新建它就會當即執行,沒法中途取消。 若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。 當處於 pending 狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

相關文章
相關標籤/搜索