45. ES6+ Promise 基礎知識
1. 前言
我們知道瀏覽器在渲染網頁時,會創建一個渲染進程進行渲染頁面,在渲染進程中其中有 GUI 渲染線程和 JS 引擎線程(如 V8 引擎)兩個線程是互斥的。也就是說在同一時間內只能有一個線程執行。如果 JavaScript 執行一段耗時程序時會阻止頁面渲染。如果要頁面快速在用戶面前呈現就要做一些優化處理。對于不能立馬得到結果的程序,不需要等待,可以放到事件隊列中,等到得到結果后再執行。
對這種不等待方式,JavaScript 提供了異步的解決方案,在 JavaScript 中常見的異步解決方案是 Callback 方式,而像 setTimeout 這樣提供異步的 API,還可以使用發布訂閱的來實現異步。使用回調函數存在回調地獄的問題。為了解決回調地獄,最早社區提出了 Promise 概念。最后在 ES6 時正式作為官方的解決方案,說明 Promise 有它獨有的優勢。本節我們將學習 Promise 的基本用法。
2. 回調地獄
我們都知道 JavaScript 異步使用的是回調函數,下面我們來看一個 ajax 請求的實例,下面的 ajax 方法是一個偽代碼,可以看作是請求接口的方法,接口請求的庫可以參考 jQuery 的 $.ajax
和 axios
。
// ajax請求的偽代碼 function ajax(url, sucessCallback, failCallback) { // url:請求的url // sucessCallback:成功的回調函數 // failCallback:失敗的回調函數 } ajax(url1, (res1) => { ajax(url2, (res2) => { ajax(url3, (res3) => { doSomething(res1, res2, res3) }) }) })
上面的 ajax 請求我們可以理解為,在調用 doSomething 方法時需要前面三個請求的結果作為參數,所以只有前一個 ajax 請求得到結果后才能發起第二個請求。這樣前后有依賴的嵌套被稱為回調地獄。對于比較復雜邏輯的情況來說,回調地獄會使程序出問題的概率大大增加。
另外,這樣做有個很嚴重的問題,就是接口請求的時間是三個請求的和,不能進行并發操作,當然我們也可以做一些優化操作,如下:
let out = after(3, function (data){ doSomething(...data) }) ajax(url1, (res1) => { out(res1) }) ajax(url2, (res2) => { out(res2) }) ajax(url3, (res3) => { out(res3) }) function after(times, callback) { const arr = []; return function (value){ arr.push(value); if (--times==0) { callback(arr); } } }
上面的代碼很優雅地解決了回調嵌套的問題,但同時我們需要手動維護一個計數器來控制最后的回調。這無疑增加了程序的復雜度,我們更希望的是關注我的業務,而不是寫更多的邏輯來優化。
針對這種情況,社區提供了很多這類優化的庫,而 Promise 則是其中最亮眼的。對上面的情況,Promise 怎么解決的呢?看如下的實現方式:
function request(url) { return new Promise((resolve, reject) => { ajax(url, (res) => { resolve(res) }) }) } Promise.all([request(url1), request(url1), request(url1)]).then((result) => { doSomething(...result) }).catch((error) => { console.log(error) })
上面的代碼中我們封裝了一個 request 請求的方法,通過?Promise.all()
?來并發請求這些接口,當接口都正確返回才會執行 then 方法中的回調,有一個錯誤都會拋出異常。這種方式比較好的是,我們對請求進行了封裝,不要再關注每一步請求是否完成做對應的邏輯處理,讓我們在開發過程中更加關注業務邏輯,使開發效率更快。
3. Promise 用法
前面我們通過一個回調地獄的案例,說明了 Promise 的優點,就是為了解決異步而產生的。并且可以處理并發請求,很好地優化了程序資源。
3.1 實例化一個 Promise
首先需要明確 Promise 是一個類,我們在 VSCode 中輸入?new Promise()
?會給我們如下的提示:
在?new Promise()
?時需要默認需要傳入一個回調函數,這個回調函數是 executor(執行器),默認會立即執行。執行器會提供兩個方法(resolve 和 reject)用于改變 promise 的狀態。resolve
?會觸發成功狀態,reject
?會觸發失敗狀態,無論成功或失敗都會傳入一個返回值,這個返回值會在實例調用?then
?方法后作為響應值獲取。
var promise = new Promise((resolve, reject) => { ajax(url, (data) => { resolve(data); // 成功 }, (error) => { reject(error); // 失敗 }) })
上面的代碼中實例化一個 ajax 請求的 Promise, 當接口請求成功就會調用 resolve()
方法把請求的值傳入進去,如果失敗了就調用 reject()
方法把錯誤信息傳入進去。在后續的鏈式調用中獲取相應的結果。
我們需要知道的是,Promise 有三個狀態:等待(pending)、成功(fulfilled),失?。╮ejected)。在初始化時,這個狀態是等待態,在等待狀態時可以轉化為成功態或失敗態。當狀態是成功態或是失敗態后不能再被改變了。
上面的代碼中可以改變 Promise 狀態的是執行器提供的 resolve 和 reject,resolve 會將等待態變為成功態,reject 則會將等待態變為失敗態,在狀態變為成功或失敗的狀態時就不能被更改了。
3.2 then
在實例化 Promise 類后,我們如何訪問成功和失敗的值呢?Promise 提供了鏈式調用的 then 方法用于訪問成功的值和失敗的值,then 提供了兩個回調 onfulfilled(成功回調)、onrejected(失敗回調)。
var promise = new Promise((resolve, reject) => { resolve(123); // reject('error') // throw new Error('Error') }) promise.then( (data) => { console.log(data) // 123 return '100' }, (reason) => { console.log(reason) } ) .then((data) => { console.log(data) // 100 }, null)
上面的代碼中給我了幾個測試用例,有興趣的小伙伴可以進行測試。then 方法返回一個值而不是 Promise 實例,并且會把這個結果返回到下一個 then 的成功回調中;
如果返回的是一個 promise,下一個 then 會采用這個 promise 結果,如果返回的是失敗,會傳到下一個 then 中失敗的回調函數中去:
var promise = new Promise((resolve, reject) => { resolve(123); }) promise.then( (data) => { return new Promise((resolve, reject) => { reject('錯誤內容'); }) }, null ) .then(null, (err) => { console.log('error:', err); // error: 錯誤內容 })
如果在失敗的回調函數中返回一個普通值或成功的 promise 也會走到下一層 then 的成功回調中去。
promise.then(null, (err) => { return '100'; }).then((data) => { console.log('data:', data); // data: 123 }, null)
通過上面的例子可以知道,當前 then 中走成功與否,主要看上一層返回的結果??偨Y有兩點。
- 當上一層返回一個普通值,或是一個成功的 Promise,則會走到下一層成功的回調函數中去;
- 如果上一層返回一個失敗的 Promise,或是拋出一個異常,則會走到下一層的失敗的回調函數中去。
4. 小結
本節主要通過 JavaScript 中回調地獄的一個案例來引出為什么使用 Promise,以及 Promise 所帶來的好處。然后學習了 Promise 的基本使用和鏈式調用 then 方法,需要注意的是,then 中執行成功或是失敗是根據它上一層的返回值,如果返回的是一個普通值或成功的 Promise 則會走 then 的成功回調;如果拋出異?;蚍祷厥〉?Promise 則走 then 的失敗回調。
1. 本站所有文章教程及資源素材均來源于網絡與用戶分享或為本站原創,僅限用于學習和研究。
2. 如果內容損害你的權益請聯系客服QQ:1642748312給予處理。
碼云筆記 » 45. ES6+ Promise 基礎知識