前端面試過程當中,基本都會問到 Promise,若是你足夠幸運,面試官問的比較淺,僅僅問 Promise 的使用方式,那麼恭喜你。事實上,大多數人並無那麼幸運。因此,咱們要準備好九淺一深的知識。前端
不知道讀者有沒有想過,爲何那麼多面試官都喜歡問Promise?能夠思考一下哦~node
咱們看一些 Promise 的常見面試問法,由淺至深。面試
這些問題,若是你都能 hold 住,那麼面試官基本承認你了。帶着上面這些問題,咱們往下看。ajax
在 Promise 出現之前,咱們處理一個異步網絡請求,大概是這樣:編程
// 請求 表明 一個異步網絡調用。 // 請求結果 表明網絡請求的響應。 請求1(function(請求結果1){ 處理請求結果1 })
看起來還不錯。
可是,需求變化了,咱們須要根據第一個網絡請求的結果,再去執行第二個網絡請求,代碼大概以下:json
請求1(function(請求結果1){ 請求2(function(請求結果2){ 處理請求結果2 }) })
看起來也不復雜。
可是。。需求是永無止境的,因而乎出現了以下的代碼:數組
請求1(function(請求結果1){ 請求2(function(請求結果2){ 請求3(function(請求結果3){ 請求4(function(請求結果4){ 請求5(function(請求結果5){ 請求6(function(請求結果3){ ... }) }) }) }) }) })
這回傻眼了。。。 臭名昭著的 回調地獄 現身了。promise
更糟糕的是,咱們基本上還要對每次請求的結果進行一些處理,代碼會更加臃腫,在一個團隊中,代碼 review 以及後續的維護將會是一個很痛苦的過程。瀏覽器
回調地獄帶來的負面做用有如下幾點:網絡
出現了問題,天然就會有人去想辦法。這時,就有人思考了,能不能用一種更加友好的代碼組織方式,解決異步嵌套的問題。
let 請求結果1 = 請求1(); let 請求結果2 = 請求2(請求結果1); let 請求結果3 = 請求3(請求結果2); let 請求結果4 = 請求2(請求結果3); let 請求結果5 = 請求3(請求結果4);
相似上面這種同步的寫法。 因而 Promise 規範誕生了,而且在業界有了不少實現來解決回調地獄的痛點。好比業界著名的 Q 和 bluebird,bluebird 甚至號稱運行最快的類庫。
看官們看到這裏,對於上面的問題 2 和問題 7 ,心中是否有了答案呢。^_^
Promise 是異步編程的一種解決方案,比傳統的異步解決方案【回調函數】和【事件】更合理、更強大。現已被 ES6 歸入進規範中。
仍是使用上面的網絡請求例子,咱們看下 Promise 的常規寫法:
new Promise(請求1) .then(請求2(請求結果1)) .then(請求3(請求結果2)) .then(請求4(請求結果3)) .then(請求5(請求結果4)) .catch(處理異常(異常信息))
比較一下這種寫法和上面的回調式的寫法。咱們不難發現,Promise 的寫法更爲直觀,而且可以在外層捕獲異步函數的異常信息。
Promise 的經常使用 API 以下:
類方法,該方法返回一個以 value 值解析後的 Promise 對象 一、若是這個值是個 thenable(即帶有 then 方法),返回的 Promise 對象會「跟隨」這個 thenable 的對象,採用它的最終狀態(指 resolved/rejected/pending/settled)
二、若是傳入的 value 自己就是 Promise 對象,則該對象做爲 Promise.resolve 方法的返回值返回。
三、其餘狀況以該值爲成功狀態返回一個 Promise 對象。
上面是 resolve 方法的解釋,傳入不一樣類型的 value 值,返回結果也有區別。這個 API 比較重要,建議你們經過練習一些小例子,而且配合上面的解釋來熟悉它。以下幾個小例子:
//若是傳入的 value 自己就是 Promise 對象,則該對象做爲 Promise.resolve 方法的返回值返回。 function fn(resolve){ setTimeout(function(){ resolve(123); },3000); } let p0 = new Promise(fn); let p1 = Promise.resolve(p0); // 返回爲true,返回的 Promise 便是 入參的 Promise 對象。 console.log(p0 === p1);
傳入 thenable 對象,返回 Promise 對象跟隨 thenable 對象的最終狀態。
ES6 Promises 裏提到了 Thenable 這個概念,簡單來講它就是一個很是相似 Promise 的東西。最簡單的例子就是 jQuery.ajax,它的返回值就是 thenable 對象。可是要謹記,並非只要實現了 then 方法就必定能做爲 Promise 對象來使用。
//若是傳入的 value 自己就是 thenable 對象,返回的 promise 對象會跟隨 thenable 對象的狀態。 let promise = Promise.resolve($.ajax('/test/test.json'));// => promise對象 promise.then(function(value){ console.log(value); });
返回一個狀態已變成 resolved 的 Promise 對象。
let p1 = Promise.resolve(123); //打印p1 能夠看到p1是一個狀態置爲resolved的Promise對象 console.log(p1)
類方法,且與 resolve 惟一的不一樣是,返回的 promise 對象的狀態爲 rejected。
實例方法,爲 Promise 註冊回調函數,函數形式:fn(vlaue){},value 是上一個任務的返回結果,then 中的函數必定要 return 一個結果或者一個新的 Promise 對象,纔可讓以後的then 回調接收。
實例方法,捕獲異常,函數形式:fn(err){}, err 是 catch 註冊 以前的回調拋出的異常信息。
類方法,多個 Promise 任務同時執行,返回最早執行結束的 Promise 任務的結果,無論這個 Promise 結果是成功仍是失敗。 。
類方法,多個 Promise 任務同時執行。
若是所有成功執行,則以數組的方式返回全部 Promise 任務的執行結果。 若是有一個 Promise 任務 rejected,則只返回 rejected 任務的結果。
以上幾種即是 Promise 的經常使用 API,掌握了這些,咱們即可以熟練使用 Promise了。
必定要多練習,熟練掌握,不然只知其一;不知其二的理解在面試時捉襟見肘。
爲了便於理解 Promise,你們除了要多加練習之外,最好的方式是可以將Promise的機制與現實生活中的例子聯繫起來,這樣才能真正獲得消化。
咱們能夠把 Promise 比做一個保姆,家裏的一連串的事情,你只須要吩咐給他,他就能幫你作,你就能夠去作其餘事情了。
好比,做爲一家之主的我,某一天要出門辦事,可是我還要買菜作飯送到老婆單位(請理解我在家裏的地位。。)
出門辦的事情很重要,買菜作飯也重要。。但我本身只能作一件事。
這時我就能夠把買菜作飯的事情交給保姆,我會告訴她:
咱們知道,上面三步都是須要消耗時間的,咱們能夠理解爲三個異步任務。利用 Promise 的寫法來書寫這個操做:
function 買菜(resolve,reject) { setTimeout(function(){ resolve(['西紅柿'、'雞蛋'、'油菜']); },3000) } function 作飯(resolve, reject){ setTimeout(function(){ //對作好的飯進行下一步處理。 resolve ({ 主食: '米飯', 菜: ['西紅柿炒雞蛋'、'清炒油菜'] }) },3000) } function 送飯(resolve,reject){ //對送飯的結果進行下一步處理 resolve('老婆的麼麼噠'); } function 電話通知我(){ //電話通知我後的下一步處理 給保姆加100塊錢獎金; }
好了,如今我整理好了四個任務,這時我須要告訴保姆,讓他按照這個任務列表去作。這個過程是必不可少的,由於若是不告訴保姆,保姆不知道須要作這些事情。。(我這個保姆比較懶)
// 告訴保姆幫我作幾件連貫的事情,先去超市買菜 new Promise(買菜) //用買好的菜作飯 .then((買好的菜)=>{ return new Promise(作飯); }) //把作好的飯送到老婆公司 .then((作好的飯)=>{ return new Promise(送飯); }) //送完飯後打電話通知我 .then((送飯結果)=>{ 電話通知我(); })
至此,我通知了保姆要作這些事情,而後我就能夠放心地去辦個人事情。
請必定要謹記:若是咱們的後續任務是異步任務的話,必須return 一個 新的 promise 對象。
若是後續任務是同步任務,只需 return 一個結果便可。
咱們上面舉的例子,除了電話通知我是一個同步任務,其他的都是異步任務,異步任務 return 的是 promise對象。
除此以外,必定謹記,一個 Promise 對象有三個狀態,而且狀態一旦改變,便不能再被更改成其餘狀態。
Promise 這麼多概念,初學者很難一會兒消化掉,那麼咱們能夠採起強制記憶法,強迫本身去記住使用過程。
首先初始化一個 Promise 對象,能夠經過兩種方式建立, 這兩種方式都會返回一個 Promise 對象。
而後調用上一步返回的 promise 對象的 then 方法,註冊回調函數。
new Promise(fn) .then(fn1(value){ //處理value })
最後註冊 catch 異常處理函數,處理前面回調中可能拋出的異常。
一般按照這三個步驟,你就可以應對絕大部分的異步處理場景。用熟以後,再去研究 Promise 各個函數更深層次的原理以及使用方式便可。
看到這裏以後,咱們便能回答上面的問題 4 和問題 5了。
Promise在初始化時,傳入的函數是同步執行的,而後註冊 then 回調。註冊完以後,繼續往下執行同步代碼,在這以前,then 中回調不會執行。同步代碼塊執行完畢後,纔會在事件循環中檢測是否有可用的 promise 回調,若是有,那麼執行,若是沒有,繼續下一個事件循環。
關於 Promise 在事件循環中還有一個 微任務的概念(microtask),感興趣的話能夠看我這篇關於nodejs 時間循環的文章 剖析nodejs的事件循環,雖然和瀏覽器端有些不一樣,可是Promise 微任務的執行時機相差不大。
ES6 出現了 generator 以及 async/await 語法,使異步處理更加接近同步代碼寫法,可讀性更好,同時異常捕獲和同步代碼的書寫趨於一致。上面的列子能夠寫成這樣:
(async ()=>{ let 蔬菜 = await 買菜(); let 飯菜 = await 作飯(蔬菜); let 送飯結果 = await 送飯(飯菜); let 通知結果 = await 通知我(送飯結果); })();
是否是更清晰了有沒有。須要記住的是,async/await也是基於 Promise 實現的,因此,咱們仍然有必要深刻理解 Promise 的用法。
相信各位看官看到這裏也累了,同時限於篇幅,本文再也不對手動實現 Promise 進行講解了,留待下一篇文章~
上面的內容但願讀者多作練習,吃透 Promise 的使用與原理,讓面試更加從容。