前言
今天來研究 Promise
很多大哥學會 **Promise
**後都覺得不過如此
但初次接觸 我完全看不懂啊~~~~~
在以往我們如果處理多層異步操作,我們往往會像下面那樣編寫我們的代碼
1 | doSomething(function(result) { |
閱讀上面代碼,是不是很難受,上述形成了經典的回調地獄
現在通過Promise
的改寫上面的代碼
1 | doSomething().then(function(result) { |
一個 promise 代表一個異步運算的最終結果
Promise 語法結構提供了更多的程式設計上的可能性,它是一個經過長時間實戰的結構,在許多知名的函式庫或框架中很早就有見到 Promise 物件的身影,例如 Dojo、jQuery、YUI、 Ember、Angular、WinJS、Q 等等,之後 Promises/A+ 社區則提供了統一的標準。在最近新一代的 ES6 標準中將會包含了 Promise 的實作,提供原生的語言內建支援,這將是個開始,往後會有愈來愈多 API 以此為基礎架構在其上
Promise
這種異步執行結構的需求,在伺服器端(Node.js)遠遠大於瀏覽器端,原因是瀏覽器通常只有會有一個使用者在操作,而且除了網路連線要求之類的 I/O(例如 AJAX)、DOM 事件處理、動畫流程處理(計時器)外,並沒有太多需要異步執行 I/O 處理的情況。伺服器端(Node.js)所面臨到情況就很嚴峻,除了與外部資源 I/O 的處理情況到處都有之外,而且伺服器端(Node.js)是需要同時服務多人使用的情況。
Promise 是一個強大的異步執行流程語法結構,在 ES6 Promise 標準中,實作內容只有一個建構函式與一個then方法、一個catch方法,再加上四個必需以Promise關鍵字
呼叫的靜態函式,Promise.resolve
、Promise.reject
、Promise.all
、Promise.race
。語法介紹頁面只有 7 頁,內容少之又少。但為何 Promise 結構不容易被理解?原因在於同步與異步回調函式執行的概念,以及其中很多流程運作,需要用另一種方式來思考要如何進行。當然,需要理解的規則也很多。
以初學者來說,從規則來理解它的運作方式,會是比較容易進入的學習路徑。
說道 Promise
前要先了解 異步 Callback(回調)
異步 Callback(回調)
Promise 中的所有回調函式,都是異步執行的
但 並非所有使用Callback Function 的API 都是異步執行的!!!!
JS語言內建 API 中使用的回調函式不一定是異步執行的,也有同步執行
的
例如 Array.forEach
要讓開發者自訂的 callbacks
(回調)的執行轉變為異步,有以下幾種方式:
- 使用計時器(timer)函式: setTimeout, setInterval
- 特殊的函式: nextTick, setImmediate
- 執行 I/O: 監聽網路、資料庫查詢或讀寫外部資源
- 訂閱事件
註: 執行 I/O 的 API 通常會出現在伺服器端(Node.js),例如讀寫檔案、資料庫互動等等,這些 API 都會經過特別的設計。瀏覽器端只有少數幾個。
在 Promise
結構中異步回調函式
只是其中一個重要的參與分子,但 Promise
的重點並不只是在異步執行的回調函式,它可以把多個異步執行的函式
,執行流程轉變為序列執行
(一個接一個),或是並行執行
(全部都要處理完再說),並且作更好的錯誤處理方式。也就是說,Promise 結構
是一種異步執行的控制流程架構
異步 Function 與 同步 Function
同步 Function
同步執行函式的結果要不就是回傳一個值,要不然就是執行到一半發生例外,中斷目前的程式然後拋出例外。
異步 Function
異步執行函式的結果要不就是帶有回傳值的成功,要不就是帶有回傳理由的失敗。
兩個看起來很像
可以理解成 ↓
- 同步,程式 發生錯誤就會停止
- 異步,程式 發生錯誤還是會繼續,只是會給你一個錯誤的理由
Promise 物件的設計就是針對異步函式的執行結果所設計的,promise 物件最後的結果要不然就用一個回傳值來 fulfilled(實現),要不然就用一個理由(錯誤)來 rejected(拒絕)。
你可能會認為這種用失敗(或拒絕)或成功的兩分法結果,似乎有點太武斷了,但在許多異步的結構中,的確是用成功或失敗來作為代表,例如 AJAX 的語法結構。promise 物件用實現(解決)與拒絕來作為兩分法的分別字詞。對於有回傳值的情況,沒有什麼太多的考慮空間,必定都是實現狀態,但對於何時才算是拒絕的狀態,這有可能需要仔細考量,例如以下的情況:
好的拒絕狀態應該是:
- I/O 操作時發生錯誤,例如讀寫檔案或是網路上的資料時,中途發生例外情況
- 無法完成預期的工作,例如accessUsersContacts函式是要讀取手機上的聯絡人名單,因為權限不足而失敗
- 內部錯誤導致無法進行異步的程序,例如環境的問題或是程式開發者傳送錯誤的傳入值
壞的拒絕狀態例如:
- 沒有找到值或是輸出是空白的情況,例如對資料庫查詢,目前沒有找到結果,回傳值是 0。它不應該是個拒絕狀態,而是帶有 0 值的實現。
- 詢問類的函式,例如hasPermissionToAccessUsersContacts函式詢問是否有讀取手機上聯絡人名單的權限,當回傳的結果是 false,也就是沒有權限時,應該是一個帶有 false 值的實現。
不同的想法會導致不同的設計,舉一個明確的實例來說明拒絕狀態的情境設計。
jQuery
的ajax()
方法,它在失敗
時會呼叫fail處理函式
,
失敗的情況除了網路連線的問題外,它會在雖然伺服器有回應,
但是是屬於失敗類型的 HTTP 狀態碼時,也算作是失敗的狀態。
但另一個可以用於類似功能的 Fetch API
並沒有,fetch使用 Promise 架構,只有在網路連線發生問題才會轉為 rejected(拒絕)狀態,只要是伺服器有回應都算已實現狀態。
註: 在 JavaScript 中函式的設計,必定有回傳值,沒寫只是回傳 undefined,相當於return undefined
Promise 狀態
promise
對象僅有三種
狀態
pending
(進行中)fulfilled
(已成功)rejected
(已失敗)
Promise 特點
- 對象的狀態不受外界影響,只有異步操作的結果,可以決定當前是哪一種狀態
- 一旦狀態改變(從
pending
變為fulfilled
和 從pending
變為rejected
),
就不會再變,任何時候都可以得到這個結果
Promise 流程
認真閱讀下圖
,我們能夠輕鬆了解promise整個流程
Promise 用法
Promise
對像是一個構造函數,用來生成Promise實例
1 | const promise = new Promise(function(resolve, reject) {}); |
Promise 代碼結構
- 在通過new創建Promise對象時,我們需要傳入一個回調函數,我們稱之為executor;
- 這個回調函數會被立即執行,並且給傳入另外兩個回調函數resolve、reject;
- 當我們調用resolve回調函數時,會執行Promise對象的then方法傳入的回調函數;
- 當我們調用reject回調函數時,會執行Promise對象的catch方法傳入的回調函數;
下面Promise使用過程,我們可以將它劃分成三個狀態
:
待定(pending): 初始狀態,既沒有被兌現,也沒有被拒絕;
- 當執行executor中的代碼時,處於該狀態;
已兌現(
fulfilled
): 意味著操作成功完成;- 執行了resolve時,處於該狀態;
已拒絕(
rejected
): 意味著操作失敗;- 執行了reject時,處於該狀態;
待定(pending ) |
已兌現(fulfilled ) |
已拒絕(rejected ) |
---|---|---|
初始狀態,既沒有被兌現,也沒有被拒絕 | 操作成功完成 | 操作失敗 |
當執行executor中的代碼時, 處於該狀態 |
執行了resolve時, 處於該狀態 |
執行了reject時, 處於該狀態 |
我們來看一下Promise
代碼結構:
1 | function foo() { |
Promise重構請求
那麼有了Promise,我們就可以將之前的代碼進行重構了:
1 | // request.js |
今天先研究到這邊
明天再來研究實例方法