一饥努、前言
前端面試過程中,基本都會問到 Promise八回,如果你足夠幸運肪凛,面試官問的比較淺,僅僅問 Promise 的使用方式辽社,那么恭喜你伟墙。事實上,大多數人并沒有那么幸運滴铅。所以戳葵,我們要準備好九淺一深的知識。
??不知道讀者有沒有想過汉匙,為什么那么多面試官都喜歡問Promise拱烁?可以思考一下哦~
二、常見 Promise 面試題
我們看一些 Promise 的常見面試問法噩翠,由淺至深戏自。
- 1、了解 Promise 嗎伤锚?
- 2擅笔、Promise 解決的痛點是什么?
- 3屯援、Promise 解決的痛點還有其他方法可以解決嗎猛们?如果有,請列舉狞洋。
- 4弯淘、Promise 如何使用?
- 5吉懊、Promise 常用的方法有哪些庐橙?它們的作用是什么假勿?
- 6、Promise 在事件循環(huán)中的執(zhí)行過程是怎樣的态鳖?
- 7转培、Promise 的業(yè)界實現都有哪些?
- 8郁惜、能不能手寫一個 Promise 的 polyfill堡距。
這些問題,如果你都能 hold 住兆蕉,那么面試官基本認可你了羽戒。帶著上面這些問題,我們往下看虎韵。
Promise 出現的原因
??在 Promise 出現以前易稠,我們處理一個異步網絡請求离唬,大概是這樣:
// 請求 代表 一個異步網絡調用噪叙。
// 請求結果 代表網絡請求的響應隘世。
請求1(function(請求結果1){
處理請求結果1
})
看起來還不錯吏砂。
但是,需求變化了彭雾,我們需要根據第一個網絡請求的結果疚顷,再去執(zhí)行第二個網絡請求壮池,代碼大概如下:
請求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){
...
})
})
})
})
})
})
這回傻眼了。腕唧。或辖。 臭名昭著的 回調地獄 現身了。
更糟糕的是枣接,我們基本上還要對每次請求的結果進行一些處理颂暇,代碼會更加臃腫,在一個團隊中但惶,代碼 review 以及后續(xù)的維護將會是一個很痛苦的過程耳鸯。
回調地獄帶來的負面作用有以下幾點:
- 代碼臃腫。
- 可讀性差榆骚。
- 耦合度過高片拍,可維護性差。
- 代碼復用性差妓肢。
- 容易滋生 bug。
- 只能在回調里處理異常苫纤。
出現了問題碉钠,自然就會有人去想辦法纲缓。這時,就有人思考了喊废,能不能用一種更加友好的代碼組織方式祝高,解決異步嵌套的問題。
let 請求結果1 = 請求1();
let 請求結果2 = 請求2(請求結果1);
let 請求結果3 = 請求3(請求結果2);
let 請求結果4 = 請求2(請求結果3);
let 請求結果5 = 請求3(請求結果4);
類似上面這種同步的寫法污筷。 于是 Promise 規(guī)范誕生了工闺,并且在業(yè)界有了很多實現來解決回調地獄的痛點。比如業(yè)界著名的 Q 和 bluebird瓣蛀,bluebird 甚至號稱運行最快的類庫陆蟆。
看官們看到這里,對于上面的問題 2 和問題 7 惋增,心中是否有了答案呢叠殷。
什么是 Promise
??Promise 是異步編程的一種解決方案,比傳統(tǒng)的異步解決方案【回調函數】和【事件】更合理诈皿、更強大×质現已被 ES6 納入進規(guī)范中。
代碼書寫比較
還是使用上面的網絡請求例子稽亏,我們看下 Promise 的常規(guī)寫法:
new Promise(請求1)
.then(請求2(請求結果1))
.then(請求3(請求結果2))
.then(請求4(請求結果3))
.then(請求5(請求結果4))
.catch(處理異常(異常信息))
比較一下這種寫法和上面的回調式的寫法壶冒。我們不難發(fā)現,Promise 的寫法更為直觀截歉,并且能夠在外層捕獲異步函數的異常信息胖腾。
API
Promise 的常用 API 如下:
- Promise.resolve(value)
類方法,該方法返回一個以 value 值解析后的 Promise 對象 1怎披、如果這個值是個 thenable(即帶有 then 方法)胸嘁,返回的 Promise 對象會“跟隨”這個 thenable 的對象,采用它的最終狀態(tài)(指 resolved/rejected/pending/settled)
2凉逛、如果傳入的 value 本身就是 Promise 對象性宏,則該對象作為 Promise.resolve 方法的返回值返回。
3状飞、其他情況以該值為成功狀態(tài)返回一個 Promise 對象毫胜。
上面是 resolve 方法的解釋,傳入不同類型的 value 值诬辈,返回結果也有區(qū)別酵使。這個 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 對象的最終狀態(tài)攻礼。
ES6 Promises 里提到了 Thenable 這個概念业踢,簡單來說它就是一個非常類似 Promise 的東西。最簡單的例子就是 jQuery.ajax礁扮,它的返回值就是 thenable 對象知举。但是要謹記,并不是只要實現了 then 方法就一定能作為 Promise 對象來使用太伊。
//如果傳入的 value 本身就是 thenable 對象雇锡,返回的 promise 對象會跟隨 thenable 對象的狀態(tài)。
let promise = Promise.resolve($.ajax('/test/test.json'));// => promise對象
promise.then(function(value){
console.log(value);
});
返回一個狀態(tài)已變成 resolved 的 Promise 對象倦畅。
let p1 = Promise.resolve(123);
//打印p1 可以看到p1是一個狀態(tài)置為resolved的Promise對象
console.log(p1)
- Promise.reject
類方法遮糖,且與 resolve 唯一的不同是,返回的 promise 對象的狀態(tài)為 rejected叠赐。
- Promise.prototype.then
實例方法欲账,為 Promise 注冊回調函數,函數形式:fn(vlaue){}芭概,value 是上一個任務的返回結果赛不,then 中的函數一定要 return 一個結果或者一個新的 Promise 對象,才可以讓之后的then 回調接收罢洲。
- Promise.prototype.catch
實例方法踢故,捕獲異常,函數形式:fn(err){}, err 是 catch 注冊 之前的回調拋出的異常信息惹苗。
- Promise.race
類方法殿较,多個 Promise 任務同時執(zhí)行,返回最先執(zhí)行結束的 Promise 任務的結果桩蓉,不管這個 Promise 結果是成功還是失敗淋纲。 。
- Promise.all
類方法院究,多個 Promise 任務同時執(zhí)行洽瞬。
如果全部成功執(zhí)行,則以數組的方式返回所有 Promise 任務的執(zhí)行結果业汰。 如果有一個 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((送飯結果)=>{
電話通知我();
})
至此睦优,我通知了保姆要做這些事情渗常,然后我就可以放心地去辦我的事情。
請一定要謹記:如果我們的后續(xù)任務是異步任務的話汗盘,必須return 一個 新的 promise 對象皱碘。
如果后續(xù)任務是同步任務,只需 return 一個結果即可隐孽。
我們上面舉的例子癌椿,除了電話通知我是一個同步任務,其余的都是異步任務菱阵,異步任務 return 的是 promise對象踢俄。
除此之外,一定謹記晴及,一個 Promise 對象有三個狀態(tài)都办,并且狀態(tài)一旦改變,便不能再被更改為其他狀態(tài)虑稼。
- pending琳钉,異步任務正在進行。
- resolved (也可以叫fulfilled)动雹,異步任務執(zhí)行成功槽卫。
- rejected,異步任務執(zhí)行失敗胰蝠。
Promise的使用總結歼培。
??Promise 這么多概念,初學者很難一下子消化掉茸塞,那么我們可以采取強制記憶法躲庄,強迫自己去記住使用過程。
-
首先初始化一個 Promise 對象钾虐,可以通過兩種方式創(chuàng)建噪窘, 這兩種方式都會返回一個 Promise 對象。
- 1效扫、new Promise(fn)
- 2倔监、Promise.resolve(fn)
然后調用上一步返回的 promise 對象的 then 方法,注冊回調函數菌仁。
-
then 中的回調函數可以有一個參數浩习,也可以不帶參數。如果 then 中的回調函數依賴上一步的返回結果济丘,那么要帶上參數谱秽。比如:
new Promise(fn) .then(fn1(value){ //處理value })
最后注冊 catch 異常處理函數洽蛀,處理前面回調中可能拋出的異常。
??通常按照這三個步驟疟赊,你就能夠應對絕大部分的異步處理場景郊供。用熟之后,再去研究 Promise 各個函數更深層次的原理以及使用方式即可近哟。
??看到這里之后驮审,我們便能回答上面的問題 4 和問題 5了。
Promsie 與事件循環(huán)
??Promise在初始化時椅挣,傳入的函數是同步執(zhí)行的头岔,然后注冊 then 回調。注冊完之后鼠证,繼續(xù)往下執(zhí)行同步代碼,在這之前靠抑,then 中回調不會執(zhí)行量九。同步代碼塊執(zhí)行完畢后,才會在事件循環(huán)中檢測是否有可用的 promise 回調颂碧,如果有荠列,那么執(zhí)行,如果沒有载城,繼續(xù)下一個事件循環(huán)肌似。
??關于 Promise 在事件循環(huán)中還有一個 微任務的概念(microtask),感興趣的話可以看另外一篇關于nodejs 時間循環(huán)的文章 剖析nodejs的事件循環(huán)诉瓦,雖然和瀏覽器端有些不同川队,但是Promise 微任務的執(zhí)行時機相差不大。
Promise 的升級
??ES6 出現了 generator 以及 async/await 語法睬澡,使異步處理更加接近同步代碼寫法固额,可讀性更好,同時異常捕獲和同步代碼的書寫趨于一致煞聪。上面的列子可以寫成這樣:
(async ()=>{
let 蔬菜 = await 買菜();
let 飯菜 = await 做飯(蔬菜);
let 送飯結果 = await 送飯(飯菜);
let 通知結果 = await 通知我(送飯結果);
})();
是不是更清晰了有沒有斗躏。需要記住的是,async/await也是基于 Promise 實現的昔脯,所以啄糙,我們仍然有必要深入理解 Promise 的用法。
結語
上面的內容只是精選面試題里的知識云稚,若是吃透 Promise 的使用與原理隧饼,就要多加練習了,這樣會讓面試更加從容碱鳞。