promise學習(1)

原文地址javascript

定義

Promise是針對異步編程的一種解決方案,可以抽象處理異步對象以及對其進行操做。Promise並非Javascript語言的擴展,只是在ES6中將其寫進了語言標準,原生Javascript提供了Promise對象,統一了用法。前端

功能

  • 能夠在必定程度上優化回調地獄問題,當須要控制多個異步操做時,代碼結構更加清晰,便於理解。
  • 提供統一的API,書寫更加規範,便於維護,便於控制異步操做。

原理

  • 一個 Promise 對象表明一個目前還不可用,可是在將來的某個時間點能夠被解析的值。
  • 能夠理解爲Promise可以以同步的方式編寫異步代碼,可是有更優的解決方式。
  • Promise的狀態

    Promise對象存在三種狀態:pending、fulfilled、rejected。其中pending 狀態最終必定會變爲fulfilled或者是rejected中的一種,且不會再發生改變。整個過程由Promise機制保證不會受到外界干擾。當狀態由pending->fulfilled時執行已完成的回調函數,由pending->rejected時,調用失敗的回調函數。一旦狀態改變,就不會再被修改而一直保持這個狀態。java

基本API

  • 實例方法git

    1. Promise#then
    2. Promise#catch
  • 靜態方法github

    1. Promise.resolve
    2. Promise.reject
    3. Promise.all
    4. Promise.race

問題&思考

什麼是同步,什麼是異步?

你們常說Javascript是單線程的語言,這當然是對的。這裏的單線程是說,在JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,每一個特定的時刻只有特定的代碼被執行,並阻塞其餘代碼。換句話說,JavaScript是單線程,可是瀏覽器是多線程的。除了負責解析JS代碼以外,還存在其餘線程,負責HTTP請求,DOM處理等等。編程

同步能夠理解成在一個函數返回時,我就拿到了須要的結果,無需等待。異步則是函數返回時,我沒有獲得應該獲得的結果,並且要在將來某一時間才能獲得。程序執行的順序和任務執行的順序並不始終保持一致。promise

前端的哪些操做須要使用異步編程?

常見的操做有接口請求,文件讀取,定時器操做等。在瀏覽器端耗時很長的操做,最好都異步執行。(鼠標點擊事件、窗口大小拖拉事件等也能夠算是異步的)瀏覽器

Promise概念和使用上有哪些須要注意的地方?

關於Promise的狀態

Promise狀態

promise對象的狀態,只有對象處於pending狀態時是可變的,一旦從Pending轉換爲FulfilledRejected以後, 這個promise對象的狀態就不會再發生任何變化。也就是說,Promise與Event等不一樣,在.then 後執行的函數能夠確定地說只會被調用一次,並且只會執行fulfilled或者rejected中的一個。多線程

建立promise對象

通常狀況下會使用new Promise建立對象,除此以外,也可使用其餘方法。異步

靜態方法Promise.resolve(value) 能夠認爲是new Promise()方法的快捷方式,同理Promise.reject(error)也是。

thenable對象&promise對象

thenable對象和promise對象並非一回事,thenable對象能夠理解爲具備.then方法的對象。

能夠經過Promise.resolve將thenable對象轉化爲promise對象。

Promise鏈式調用

promise的鏈式調用能夠經過使用.then處理回調函數比較多的狀況,必定程度上解決回調地獄的問題。鏈式調用的原理(後續能夠單獨介紹下)簡單來講是Promise內部有一個defers隊列存放事件,程序執行時.then將下個事件放入,promise狀態變化結束以後出發響應函數執行,而後將事件從隊列中移除。由於每一次調用.then時都會建立一個全新的promise對象,因此保證了鏈式調用能夠順利執行。

鏈式調用的參數傳遞順序

demo1

前一個task的返回值做爲後一個task的參數。不使用鏈式調用,直接調用.then方法時,因爲每次都是新建的promise對象,因此value的值始終爲100。

// 1: 對同一個promise對象同時調用 then 方法
var aPromise = new Promise(function (resolve) {
    resolve(100);
});
aPromise.then(function (value) {
    return value * 2;
});
aPromise.then(function (value) {
    return value * 2;
});
aPromise.then(function (value) {
    console.log("1: " + value); // => 100
})
// vs
// 2: 對 then 進行 promise chain 方式進行調用
var bPromise = new Promise(function (resolve) {
    resolve(100);
});
bPromise.then(function (value) {
    return value * 2;
}).then(function (value) {
    return value * 2;
}).then(function (value) {
    console.log("2: " + value); // => 100 * 2 * 2
});
鏈式調用多個task的執行順序

在鏈式調用過程當中,每個.then調用時候都會建立一個全新的promise對象,.then調用結束時會執行對應狀態的回調函數,

可是若是沒有傳入rejected的回調,或者.catch使用的位置不一樣,都會致使最後的結果不一樣。

demo2

task所有正常執行:

function taskA() {
    console.log("Task A");
}
function taskB() {
    console.log("Task B");
}
function onRejected(error) {
    console.log("Catch Error: A or B", error);
}
function finalTask() {
    console.log("Final Task");
}
var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);

輸出結果爲:

Task A
Task B
Final Task

當taskA執行失敗時:

function taskA() {
    console.log("Task A");
    throw new Error("throw Error @ Task A")
}

輸出結果爲:

Task A
Error: throw Error @ Task A
Final Task

輸出結果

因爲taskB中並無註冊rejected函數,因此taskB並未執行,而是直接經過catch捕獲到了失敗。

若是在taskB的then中傳入了失敗的回調:

promise
    .then(taskA)
    .then(taskB,function(error) {
        console.log('b-catch-error')
    })
    .catch(onRejected)
    .then(finalTask);

執行結果爲:

Task A
b-catch-error
Final Task

因此,在多個.then鏈式調用時,不一樣位置報錯會致使task執行順序不一樣,執行不一樣,也許不符合預期。

因此,能夠在自行使用try-catch,保證.then執行不會出錯,或者使用其餘異步編程方式。

每一次調用.then,都會返回新的Promise對象

demo3

下面代碼中每一次返回的promise對象都不是同一個對象。

var aPromise = new Promise(function (resolve) {
    resolve(100);
});
var thenPromise = aPromise.then(function (value) {
    console.log(value);
});
var catchPromise = thenPromise.catch(function (error) {
    console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(thenPromise !== catchPromise);// => true

demo3

理解了這個再來看下面的問題。

demo4 錯誤的調用

function badAsyncCall() {
    var promise = Promise.resolve();
    promise.then(function() {
        // 任意處理
        return 1;
    });
    return promise;
}

demo5 正確的調用

function anAsyncCall() {
    var promise = Promise.resolve();
    return promise.then(function() {
        // 任意處理
        return 1;
    });
}

返回結果

結果

demo4中return 的promise並非執行過.then的promise,因此並無返回預期的結果。在實際開發中要注意這個問題。

rejected並不必定會捕獲到異常

但看這個字面上可能會有些疑惑,實際上仍是概念上的問題。

使用promise.then(Fulfilled, Rejected)的話,在Fulfilled 中發生異常的話,在Rejected中是捕獲不到這個異常的。緣由是promise狀態只能從pending變成fulfilled或者是rejected,且變化完成以後不會再被修改。因此一旦執行了fulfilled,說明promise成功了,此時就已經不會再執行rejected了。

promise 不要在異步和同步混合使用

demo6

function onReady(fn) {
    var readyState = document.readyState;
    if (readyState === 'interactive' || readyState === 'complete') {
        fn();
    } else {
        window.addEventListener('DOMContentLoaded', fn);
    }
}
onReady(function () {
    console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');

若是這段代碼在源文件中出現的位置不一樣,在控制檯上打印的log消息順序也會不一樣。 結果不可控這是極力要避免的。

正確的寫法應該是:

function onReadyPromise() {
    return new Promise(function (resolve, reject) {
        var readyState = document.readyState;
        if (readyState === 'interactive' || readyState === 'complete') {
            resolve();
        } else {
            window.addEventListener('DOMContentLoaded', resolve);
        }
    });
}
onReadyPromise().then(function () {
    console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');

爲了不上述中同時使用同步、異步調用可能引發的混亂問題,Promise在規範上規定 Promise只能使用異步調用方式

異步編程的方案

(後續學習再補充)

相關文章
相關標籤/搜索