深刻學習Promise

前言

若是你看了不少的Promise的文檔和資源,可是卻發現本身老是掌握不了Promise的精髓,也許你能夠看看個人的這篇文章,或許對你有所幫助。git

JS的運行機制

在學習使用Promise以前,須要對JS的運行機制有所瞭解。es6

JavaScript的併發模型基於"事件循環",這個模型與像 C 或者 Java 這種其它語言中的模型大相徑庭。github

可視化描述

clipboard.png

函數調用造成了一個棧幀。segmentfault

function foo(b) {
  var a = 10;
  return a + b + 11;
}

function bar(x) {
  var y = 3;
  return foo(x * y);
}

console.log(bar(7));

當調用bar時,建立了第一個幀 ,幀中包含了bar的參數和局部變量。當bar調用foo時,第二個幀就被建立,並被壓到第一個幀之上,幀中包含了foo的參數和局部變量。當foo返回時,最上層的幀就被彈出棧(剩下bar函數的調用幀 )。當bar返回的時候,棧就空了。promise

對象被分配在一個堆中,即用以表示一個大部分非結構化的內存區域。併發

隊列

一個 JavaScript 運行時包含了一個待處理的消息隊列。每個消息都與一個函數相關聯。當棧擁有足夠內存時,從隊列中取出一個消息進行處理。這個處理過程包含了調用與這個消息相關聯的函數(以及於是建立了一個初始堆棧幀)。當棧再次爲空的時候,也就意味着消息處理結束。異步

事件循環

之因此稱爲事件循環,是由於它常常被用於相似以下的方式來實現:函數

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

若是當前沒有任何消息queue.waitForMessage 會等待同步消息到達。 oop

參考資料: 併發模型與事件循環學習

Promise回顧

Promise實際上是一個函數,它的實例是一個不可逆的狀態機。一般可使用下面這幾種方式去建立一個Promise的實例。

使用 new

const promise = new Promise((resolve, reject) => {
    ......
    resolve(xxx)
    ......
});

使用靜態API

Promise.resolve, Promise.reject, Promise.all, Promise.race等提供的靜態方法,執行完畢後,也會返回一個Promise。

const promise = Promise.resolve('complete');
// promise instanceof Promise  => true

添加回調函數返回一個Promise

當咱們在一個Promise的實例中,使用then, catch, finally添加完回調函數也會返回一個Promise。

const promise = Promise.resolve('complete').then((val)=> {
    console.log(val) // complete
})
// promise instanceof Promise  => true

Promise的實例狀態:

  • pending: 初始狀態,既不是成功,也不是失敗狀態。
  • fulfilled: 意味着操做成功完成。
  • rejected: 意味着操做失敗。

當Promise的實例狀態由 pending => fulfilled, 會觸發then中註冊的第一個函數。
當Promise的實例狀態由 pending => rejected, 會觸發then中註冊的第二函數或者catch中註冊的函數。
當Promise的實例狀態發生變化後,finally註冊的函數都會觸發。

Promise 運行機制

瞭解Promise的運行機制,可以幫助咱們更好的使用Promise。下面這張圖是根據 es6-promise 的實現而描繪的Promise的運行機制。

clipboard.png

前面已經給你們介紹了JS的運行機制,JS是基於事件循環,當JS運行的消息隊列被清空後,將會去異步隊列中獲取消息,加入JS的運行隊列。 而在有了Promise之後, 將會在獲取異步隊列消息以前,會把Promise的運行隊列所有添加到JS運行消息隊列。在此運行期間, 若是Promise運行隊列又添加了新的回調函數, Promise運行隊列又會從新添加到JS運行的消息隊列中, 直到Promise的運行隊列和JS運行的消息隊列都清空,纔會去異步隊列中獲取消息。這樣就能保證Promise的回調函數都在異步調用以前。可是開發時候也須要防止進入Promise的回調陷阱,就像下面這樣

function bar() {
    Promise.resolve('resolve').then((val) => {
        console.log(val);
        bar();
    })
}
bar()
console.log('continue');
setTimeout(() => {console.log('setTimeout')});
// continue resolve resolve resolve resolve ......

上面的例子中setTimeout將永遠不會被輸出。

Promise實例的任何狀態(pending, resolved, rejected)都能使用then, catch, finally來註冊(添加)回調函數。而不一樣的是,在pending狀態的Promise實例會把這些回調函數存儲在內部,等到狀態發生改變的時候,再把的相關回調函數所有推送promise的運行隊列。而處在resolved與rejected狀態的實例,將會當即把相關函數推送到Promise的運行隊列,而不須要等待。

var callback;
var promise = new Promise((resolve) => {
   callback = resolve;
})

promise.then(() => {console.log('1')});
Promise.resolve('2').then((val) => {console.log(val)}); 
callback();
// 輸出 2 1

上面例子中的promise將會在callback執行以後,纔會把所註冊的函數推入Promise的運行隊列,因此致使所註冊的函數運行在後面。

Promise的實例狀態爲resolved時。只會把then中resolve和finally所註冊函數添加到Promise的運行隊列。並且它們的執行順序只與它們的添加順序有關。

var promise = new Promise((resolve, reject) => {
    resolve('ok');
})

promise.finally(() => {
   console.log('finally');
});
promise.then((val) => {
  console.log(val);
});
promise.catch(() => {
  console.log('catch');
});

// finally ok

很明顯 finally 出如今 ok 以前,catch所註冊的函數也將不會被推送到Promise的運行隊列,也將不會被執行。

Promise的實例狀態爲rejected的時。會把then中reject和catch與finally所註冊函數添加到Promise的運行隊列。並且它們的執行順序只與它們的添加順序有關。

var promise  = Promise.reject('reject');
promise.finally(() => {console.log('finally')}); 
promise.catch(() => {console.log('catch')}); 
promise.then(() => {console.log('ok')}, (val) => {console.log(val)});
// finally catch reject

上面就是我的關於Promise的探究與學習,與此相輔的還有另一篇,深刻學習Promise調用鏈

相關文章
相關標籤/搜索