認識 Promise
前言:為什么會出現 Promise?
最常見的一個場景就是 ajax 請求,通俗來說,由于網速的不同,可能你得到返回值的時間也是不同的,這個時候我們就需要等待,結果出來了之后才知道怎么樣繼續下去。
在 ajax 的原生實現中,利用了 onreadystatechange
事件,當該事件觸發并且符合一定條件時,才能拿到想要的數據,之后才能開始處理數據,這樣做看上去并沒有什么麻煩,但如果這個時候,我們還需要另外一個 ajax 請求,這個新 ajax 請求的其中一個參數,得從上一個 ajax 請求中獲取,這個時候我們就不得不等待上一個接口請求完成之后,再請求后一個接口。
let xhr = new XMLHttpRequest();
xhr.open('get', 'https://...');
xhr.send();
xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status >= 200 && xhr.status < 300) {console.log(xhr.responseText)//偽代碼....let xhr = new XMLHttpRequest();xhr.open('get','http://www.xx.com?a'+xhr.responseText);xhr.send();xhr.onreadystatechange = function(){if(xhr.readyState === 4){if(xhr.status>=200 && xhr.status<300){console.log(xhr.responseText)}}}}}
}
當出現第三個 ajax(甚至更多)仍然依賴上一個請求時,我們的代碼就變成了一場災難。
這場災難,往往也被稱為回調地獄。
因此我們需要一個叫做 Promise 的東西,來解決這個問題,當然,除了回調地獄之外,還有個非常重要的需求就是:為了代碼更加具有可讀性和可維護性,我們需要將數據請求與數據處理明確的區分開來。
上面的寫法,是完全沒有區分開,當數據變得復雜時,也許我們自己都無法輕松維護自己的代碼了。這也是模塊化過程中,必須要掌握的一個重要技能,請一定重視。
1. Promise 是什么?
Promise 是異步編程的一種解決方案,比傳統的解決方案回調函數更合理、更強大。
ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。
指定回調函數的方式也變得更加靈活易懂,也解決了異步回調地獄的問題
舊方案是單純使用回調函數,常見的異步操作有:定時器、fs 模塊、ajax、數據庫操作
- 從語法上說,Promise 是一個構造函數;
- 從功能上說,Promise 對象用來封裝一個異步操作并可以獲取其成功 / 失敗的結果值。
2. Promise 的優點
-
指定回調函數的方式更加靈活
-
舊的方法:必須在啟動異步任務前指定
-
promise:啟動異步任務 -> 返回 promise 對象 -> 給 promise 對象綁定回調函數
(甚至可以在異步任務結束后指定多個)
-
-
可以解決回調地獄問題,支持鏈式調用
-
什么是回調地獄?
回調函數嵌套調用,外部回調函數異步執行的結果是嵌套的回調執行的條件
-
回調地獄的缺點?
不便于閱讀、不便于異常處理
-
解決方案?
promise鏈式調用
-
終極解決方案
async / await
-
3. 定義 Promise 對象
new Promise((resolve, reject) => { ... });
3.1 Promise 實例對象的兩個屬性
-
PromiseState
:此屬性為 promise 對象的狀態屬性。
- fulfilled:成功的狀態
- rejected:失敗的狀態
- pending:初始化的狀態
【注】狀態只能由 pending -> fulfilled 或是 pending -> rejected
-
PromiseResult
:此屬性為 promise 對象的結果值(resolve 以及 reject 函數的形參值)
3.2 Promise 實例對象的兩個參數
-
resolve
修改 promise 對象的狀態,由 pending 修改為 fulfilled;
將實參設置到這個屬性 PromiseResult 中。
-
reject
修改 promise 對象的狀態,由 pending 修改為 rejected;
將實參設置到這個屬性 PromiseResult 中。
let p = new Promise((resolve, reject) => {// 調整狀態// reject(new Error("error")); // 狀態為 rejectedresolve("success"); // 狀態為 resolved
});
console.log(p); // Promise { <pending> }
4. Promise 對象的狀態
Promise 對象通過自身的狀態來控制異步操作,Promise 實例具有三種狀態.
- 異步操作未完成:pending
- 異步操作成功:fulfilled
- 異步操作失敗:rejected
這三種的狀態的變化途徑只有兩種
- 從 pending(未完成)到 fulfilled(成功)
- 從 pending(未成功)到 rejected(失敗)
一旦狀態發生變化,就凝固了,不會再有新的狀態變化,這也是 Promise 這個名字的由來,它的英語意思 “承諾”,一旦承諾生效,就不得再改變了,這也意味著 Promise 實例的狀態變化只可能發生一次。
在 Promise 對象的構造函數中,將一個函數作為第一個參數。而這個函數,就是用來處理 Promise 的狀態變化。
上面的 resolve 和 reject 都為一個函數,他們的作用分別是將狀態修改為 resolved 或 rejected。
因此,Promise 的最終結果只有兩種情況:
- 異步操作成功,Promise 實例傳回一個值(value),狀態變為 fulfilled;
- 異步操作失敗,Promise 實例拋出一個錯誤(error),狀態變為 rejected
5. then 方法(重要)
實例化 Promise 時,使用回調函數作為參數,回調函數通常有兩個參數:
-
resolve 參數
當執行到
resolve( ... )
時,會調用 then 方法中的第一個參數(回調); -
reject 參數
當執行到
reject( ... )
時,會調用 then 方法中的第二個參數(回調);
then 方法中通常有兩個回調函數作為參數,第一個回調在成功時(resolve
)調用,第二個回調在出錯時(reject
)調用,第二個參數可以省略。
5.1 then 方法返回結果
調用 then 方法的返回結果是 Promise 對象,對象狀態由回調函數的執行結果決定:
-
返回結果是非 Promise 類型的屬性
返回狀態 resolved(成功),返回值為對象成功的值
const result = p.then((data) => {console.log(data);return 123;},(error) => { console.warn(error); } );console.log(result); // 返回值為 123
如果未使用 return 進行返回,則返回值為 undefined。
-
返回 Promise 對象
返回值和返回狀態均由返回的 promise 對象的返回值和狀態決定
const result = p.then((data) => {console.log(data);return new Promise((resolve, reject) => {resolve("ok");// reject("出錯了");});},(error) => { console.warn(error); } ); console.log(result); // 返回狀態為 resolved,返回值為 ok // console.log(result); // 返回狀態為 rejected,返回值為 出錯了
-
拋出錯誤
返回狀態 rejected(失敗)
const result = p.then((data) => {console.log(data);// throw new Error("出錯了");throw "出錯了";},(error) => { console.warn(error); } );console.log(result); // 返回狀態為 rejected,返回值為 出錯了
5.2 then 方法的鏈式調用
由于 promise 可以返回 promise 對象,因此可以進行鏈式調用
// 鏈式調用
p.then((data) => {},(error) => {}
).then((data) => {},// 失敗回調可以省略
)...;