面試精選之Promise

前端面試過程當中,基本都會問到 Promise,若是你足夠幸運,面試官問的比較淺,僅僅問 Promise 的使用方式,那麼恭喜你。事實上,大多數人並無那麼幸運。因此,咱們要準備好九淺一深的知識。前端

不知道讀者有沒有想過,爲何那麼多面試官都喜歡問Promise?能夠思考一下哦~node

常見 Promise 面試題

咱們看一些 Promise 的常見面試問法,由淺至深。面試

  • 一、瞭解 Promise 嗎?
  • 二、Promise 解決的痛點是什麼?
  • 三、Promise 解決的痛點還有其餘方法能夠解決嗎?若是有,請列舉。
  • 四、Promise 如何使用?
  • 五、Promise 經常使用的方法有哪些?它們的做用是什麼?
  • 六、Promise 在事件循環中的執行過程是怎樣的?
  • 七、Promise 的業界實現都有哪些?
  • 八、能不能手寫一個 Promise 的 polyfill。

這些問題,若是你都能 hold 住,那麼面試官基本承認你了。帶着上面這些問題,咱們往下看。ajax

Promise 出現的緣由

在 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 以及後續的維護將會是一個很痛苦的過程。瀏覽器

回調地獄帶來的負面做用有如下幾點:網絡

  • 代碼臃腫。
  • 可讀性差。
  • 耦合度太高,可維護性差。
  • 代碼複用性差。
  • 容易滋生 bug。
  • 只能在回調裏處理異常。

出現了問題,天然就會有人去想辦法。這時,就有人思考了,能不能用一種更加友好的代碼組織方式,解決異步嵌套的問題。

let 請求結果1 = 請求1();
let 請求結果2 = 請求2(請求結果1); 
let 請求結果3 = 請求3(請求結果2); 
let 請求結果4 = 請求2(請求結果3); 
let 請求結果5 = 請求3(請求結果4);

相似上面這種同步的寫法。 因而 Promise 規範誕生了,而且在業界有了不少實現來解決回調地獄的痛點。好比業界著名的 Qbluebirdbluebird 甚至號稱運行最快的類庫。

看官們看到這裏,對於上面的問題 2 和問題 7 ,心中是否有了答案呢。^_^

什麼是 Promise

Promise 是異步編程的一種解決方案,比傳統的異步解決方案【回調函數】和【事件】更合理、更強大。現已被 ES6 歸入進規範中。

代碼書寫比較

仍是使用上面的網絡請求例子,咱們看下 Promise 的常規寫法:

new Promise(請求1)
    .then(請求2(請求結果1))
    .then(請求3(請求結果2))
    .then(請求4(請求結果3))
    .then(請求5(請求結果4))
    .catch(處理異常(異常信息))

比較一下這種寫法和上面的回調式的寫法。咱們不難發現,Promise 的寫法更爲直觀,而且可以在外層捕獲異步函數的異常信息。

API

Promise 的經常使用 API 以下:

  • Promise.resolve(value)

類方法,該方法返回一個以 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)
  • Promise.reject

類方法,且與 resolve 惟一的不一樣是,返回的 promise 對象的狀態爲 rejected。

  • Promise.prototype.then

實例方法,爲 Promise 註冊回調函數,函數形式:fn(vlaue){},value 是上一個任務的返回結果,then 中的函數必定要 return 一個結果或者一個新的 Promise 對象,纔可讓以後的then 回調接收。

  • Promise.prototype.catch

實例方法,捕獲異常,函數形式:fn(err){}, err 是 catch 註冊 以前的回調拋出的異常信息。

  • Promise.race

類方法,多個 Promise 任務同時執行,返回最早執行結束的 Promise 任務的結果,無論這個 Promise 結果是成功仍是失敗。 。

  • Promise.all

類方法,多個 Promise 任務同時執行。
若是所有成功執行,則以數組的方式返回全部 Promise 任務的執行結果。 若是有一個 Promise 任務 rejected,則只返回 rejected 任務的結果。

  • ...

以上幾種即是 Promise 的經常使用 API,掌握了這些,咱們即可以熟練使用 Promise了。

必定要多練習,熟練掌握,不然只知其一;不知其二的理解在面試時捉襟見肘。

如何理解 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 對象有三個狀態,而且狀態一旦改變,便不能再被更改成其餘狀態。

  • pending,異步任務正在進行。
  • resolved (也能夠叫fulfilled),異步任務執行成功。
  • rejected,異步任務執行失敗。

Promise的使用總結。

Promise 這麼多概念,初學者很難一會兒消化掉,那麼咱們能夠採起強制記憶法,強迫本身去記住使用過程。

  • 首先初始化一個 Promise 對象,能夠經過兩種方式建立, 這兩種方式都會返回一個 Promise 對象。

    • 一、new Promise(fn)
    • 二、Promise.resolve(fn)
  • 而後調用上一步返回的 promise 對象的 then 方法,註冊回調函數。

    • then 中的回調函數能夠有一個參數,也能夠不帶參數。若是 then 中的回調函數依賴上一步的返回結果,那麼要帶上參數。好比
    new Promise(fn)
        .then(fn1(value){
            //處理value
        })
  • 最後註冊 catch 異常處理函數,處理前面回調中可能拋出的異常。

一般按照這三個步驟,你就可以應對絕大部分的異步處理場景。用熟以後,再去研究 Promise 各個函數更深層次的原理以及使用方式便可。

看到這裏以後,咱們便能回答上面的問題 4 和問題 5了。

Promsie 與事件循環

Promise在初始化時,傳入的函數是同步執行的,而後註冊 then 回調。註冊完以後,繼續往下執行同步代碼,在這以前,then 中回調不會執行。同步代碼塊執行完畢後,纔會在事件循環中檢測是否有可用的 promise 回調,若是有,那麼執行,若是沒有,繼續下一個事件循環。

關於 Promise 在事件循環中還有一個 微任務的概念(microtask),感興趣的話能夠看我這篇關於nodejs 時間循環的文章 剖析nodejs的事件循環,雖然和瀏覽器端有些不一樣,可是Promise 微任務的執行時機相差不大。

Promise 的升級

ES6 出現了 generator 以及 async/await 語法,使異步處理更加接近同步代碼寫法,可讀性更好,同時異常捕獲和同步代碼的書寫趨於一致。上面的列子能夠寫成這樣:

(async ()=>{
    let 蔬菜 = await 買菜();
    let 飯菜 = await 作飯(蔬菜);
    let 送飯結果 = await 送飯(飯菜);
    let 通知結果 = await 通知我(送飯結果);
})();

是否是更清晰了有沒有。須要記住的是,async/await也是基於 Promise 實現的,因此,咱們仍然有必要深刻理解 Promise 的用法。

結語

相信各位看官看到這裏也累了,同時限於篇幅,本文再也不對手動實現 Promise 進行講解了,留待下一篇文章~

上面的內容但願讀者多作練習,吃透 Promise 的使用與原理,讓面試更加從容。

相關文章
相關標籤/搜索