promise經(jīng)典面試題

上期講了promise基本概念和用法,今天結(jié)合上期的內(nèi)容苟弛,講解幾道經(jīng)典的相關(guān)面試題灸促。

promise基本規(guī)則:

1. 首先Promise構(gòu)造函數(shù)會(huì)立即執(zhí)行异旧,而Promise.then()內(nèi)部的代碼在當(dāng)次事件循環(huán)的結(jié)尾立即執(zhí)行(微任務(wù))。

2. promise的狀態(tài)一旦由等待pending變?yōu)槌晒?code>fulfilled或者失敗rejected佣谐。那么當(dāng)前promise被標(biāo)記為完成肚吏,后面則不會(huì)再次改變?cè)摖顟B(tài)。

3. resolve函數(shù)和reject函數(shù)都將當(dāng)前Promise狀態(tài)改為完成狭魂,并將異步結(jié)果罚攀,或者錯(cuò)誤結(jié)果當(dāng)做參數(shù)返回。

4. Promise.resolve(value)

返回一個(gè)狀態(tài)由給定 value 決定的 Promise 對(duì)象雌澄。如果該值是 thenable(即斋泄,帶有 then 方法的對(duì)象),返回的 Promise 對(duì)象的最終狀態(tài)由 then 方法執(zhí)行決定镐牺;否則的話(該 value 為空炫掐,基本類型或者不帶 then 方法的對(duì)象),返回的 Promise 對(duì)象狀態(tài)為 fulfilled,并且將該 value 傳遞給對(duì)應(yīng)的 then 方法睬涧。通常而言募胃,如果你不知道一個(gè)值是否是 Promise 對(duì)象旗唁,使用 Promise.resolve(value) 來(lái)返回一個(gè) Promise 對(duì)象,這樣就能將該 value 以 Promise 對(duì)象形式使用。

5. Promise.all(iterable)/Promise.race(iterable)

簡(jiǎn)單理解痹束,這2個(gè)函數(shù)检疫,是將接收到的promise列表的結(jié)果返回,區(qū)別是祷嘶,all是等待所有的promise都觸發(fā)成功了电谣,才會(huì)返回,而arce有一個(gè)成功了就會(huì)返回結(jié)果抹蚀。其中任何一個(gè)promise執(zhí)行失敗了剿牺,都會(huì)直接返回失敗的結(jié)果。

6. promise對(duì)象的構(gòu)造函數(shù)只會(huì)調(diào)用一次环壤,then方法和catch方法都能多次調(diào)用晒来,但一旦有了確定的結(jié)果,再次調(diào)用就會(huì)直接返回結(jié)果郑现。

開始答題

題目一

const promise = new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
    reject('error');
})
promise.then(() => {
    console.log(3);
}).catch(e => console.log(e))
console.log(4);

可以看:規(guī)則一湃崩,promise構(gòu)造函數(shù)的代碼會(huì)立即執(zhí)行,then或者reject里面的代碼會(huì)放入異步微任務(wù)隊(duì)列接箫,在宏任務(wù)結(jié)束后會(huì)立即執(zhí)行攒读。規(guī)則二:promise的狀態(tài)一旦變更為成功或者失敗,則不會(huì)再次改變辛友,所以執(zhí)行結(jié)果為:1,2,4,3薄扁。而catch里面的函數(shù)不會(huì)再執(zhí)行。

題目二

const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
             console.log('once')
             resolve('success')
        }, 1000)
 })
promise.then((res) => {
       console.log(res)
     })
promise.then((res) => {
     console.log(res)
 })

根據(jù)規(guī)則6废累,promise的構(gòu)造函數(shù)只會(huì)執(zhí)行一次邓梅,而then方法可以多次調(diào)用,但是第二次是直接返回結(jié)果邑滨,不會(huì)有異步等待的時(shí)間日缨,所以執(zhí)行結(jié)果是: 過(guò)一秒打印:once,success,success

題目三

在瀏覽器上掖看,下面的程序會(huì)一次輸出哪些內(nèi)容匣距?

const p1 = () => (new Promise((resolve, reject) => {
    console.log(1);
    let p2 = new Promise((resolve, reject) => {
        console.log(2);
        const timeOut1 = setTimeout(() => {
            console.log(3);
            resolve(4);
        }, 0)
        resolve(5);
    });
    resolve(6);
    p2.then((arg) => {
        console.log(arg);
    });

}));
const timeOut2 = setTimeout(() => {
    console.log(8);
    const p3 = new Promise(reject => {
        reject(9);
    }).then(res => {
        console.log(res)
    })
}, 0)


p1().then((arg) => {
    console.log(arg);
});
console.log(10);

事件循環(huán):javascript的執(zhí)行規(guī)則里面有個(gè)事件循環(huán)Event Loot的規(guī)則,在事件循環(huán)中哎壳,異步事件會(huì)放到異步隊(duì)列里面毅待,但是異步隊(duì)列里面又分為宏任務(wù)和微任務(wù),瀏覽器端的宏任務(wù)一般有:script標(biāo)簽,setTimeout,setInterval,setImmediate,requestAnimationFrame耳峦。微任務(wù)有:MutationObserver,Promise.then catch finally恩静。宏任務(wù)會(huì)阻塞瀏覽器的渲染進(jìn)程,微任務(wù)會(huì)在宏任務(wù)結(jié)束后立即執(zhí)行,在渲染之前驶乾。

回到題目邑飒,結(jié)果為:'1,2,10,5,6,8,9,3'。你答對(duì)了嗎级乐?如果對(duì)了疙咸,那你基本理解了事件隊(duì)列,微任務(wù)风科,宏任務(wù)了撒轮。

第一步:執(zhí)行宏任務(wù),結(jié)合規(guī)則一贼穆,輸出:1,2,10题山。這時(shí)候事件循環(huán)里面有異步任務(wù)timeOut1,timeOut2,p2.then,p1.then

第二步:宏任務(wù)執(zhí)行完后Event Loop會(huì)去任務(wù)隊(duì)列取異步任務(wù)故痊,微任務(wù)會(huì)優(yōu)先執(zhí)行顶瞳,這時(shí)候會(huì)先后執(zhí)行p2.then,p1.then,打印5,6愕秫。

第三步:微任務(wù)執(zhí)行完了慨菱,開始宏任務(wù),由于2個(gè)settimeout等待時(shí)間一樣戴甩,所以會(huì)執(zhí)行先進(jìn)入異步隊(duì)列的timeOut2,先后打臃取:8。執(zhí)行宏任務(wù)的過(guò)程中甜孤,p3.then微任務(wù)進(jìn)入了隊(duì)列协饲,宏任務(wù)執(zhí)行完畢會(huì)執(zhí)行微任務(wù),輸出:9课蔬。之后執(zhí)行timeOut1,輸出:3囱稽。

第四步:結(jié)合規(guī)則6郊尝,由于p2這個(gè)Promise對(duì)象的執(zhí)行結(jié)果已經(jīng)確定二跋,所以4不會(huì)被打印。

注:在node.js上輸出結(jié)果并不是這樣的流昏,因?yàn)?code>node.js的事件循環(huán)跟瀏覽器端的有區(qū)別扎即。

題目四

在不使用async/await的情況下,順序執(zhí)行一組異步代碼函數(shù)况凉,并輸出最后的結(jié)果谚鄙。

在上篇文章中,已經(jīng)講到過(guò)刁绒,利用promise.resolve結(jié)合reduce能順序執(zhí)行一組異步函數(shù)闷营。

const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...dd) => x => dd.reduce(applyAsync, Promise.resolve(x));
const transformData = composeAsync(funca, funcb, funcc, funcd);
transformData(1).then(result => console.log(result,'last result')).catch(e => console.log(e));

以上代碼可以封裝成工具來(lái)使用,利用的是規(guī)則4,promise.resolve函數(shù)的特點(diǎn)傻盟,其中dd可以是一組同步函數(shù)速蕊,也可以是異步函數(shù)。最后的結(jié)果在result里面娘赴,異常信息能在最后捕獲规哲。想看更具體的可以查看這篇文章:
promise講解

題目五

順序加載10張圖片,圖片地址已知诽表,但是同時(shí)最多加載3張圖片唉锌,要求用promise實(shí)現(xiàn)。

const baseUrl = 'http://img.aizhifou.cn/';
const urls = ['1.png', '2.png', '3.png', '4.png', '5.png','6.png', '7.png', '8.png', '9.png', '10.png'];
const loadImg = function (url, i) {
    return new Promise((resolve, reject) => {
        try {
            // 加載一張圖片
            let image = new Image();
            image.onload = function () {
                resolve(i)
            }
            image.onerror = function () {
                reject(i)
            };
            image.src = baseUrl + url;
        } catch (e) {
            reject(i)
        }
    })
}
function startLoadImage(urls, limits, endHandle) {
    // 當(dāng)前存在的promise隊(duì)列
    let promiseMap = {};
    // 當(dāng)前索引對(duì)應(yīng)的加載狀態(tài)竿奏,無(wú)論成功袄简,失敗都會(huì)標(biāo)記為true,格式: {0: true, 1: true, 2: true...}
    let loadIndexMap = {};
    // 當(dāng)前以及加載到的索引泛啸,方便找到下一個(gè)未加載的索引痘番,為了節(jié)省性能,其實(shí)可以不要
    let loadIndex = 0;
    const loadAImage = function () {
        // 所有的資源都進(jìn)入了異步隊(duì)列
        if (Object.keys(loadIndexMap).length === urls.length) {
            // 所有的資源都加載完畢平痰,或者進(jìn)入加載狀態(tài)汞舱,遞歸結(jié)束
            const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, [])
            Promise.all(promiseList).then(res => {
                // 這里如果沒有加載失敗,就會(huì)在所有加載完畢后執(zhí)行宗雇,如果其中某個(gè)錯(cuò)誤了昂芜,這里的結(jié)果就不準(zhǔn)確,不過(guò)這個(gè)不是題目要求的赔蒲。
                console.log('all');
                endHandle && endHandle()
            }).catch((e) => {
                console.log('end:' + e);
            })
        } else {
            // 遍歷泌神,知道里面有3個(gè)promise
            while (Object.keys(promiseMap).length < limits) {
                for (let i = loadIndex; i < urls.length; i++) {
                    if (loadIndexMap[i] === undefined) {
                        loadIndexMap[i] = false;
                        promiseMap[i] = loadImg(urls[i], i);
                        loadIndex = i;
                        break;
                    }
                }
            }
            // 獲取當(dāng)前正在進(jìn)行的promise列表,利用reduce從promiseMap里面獲取
            const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, [])
            Promise.race(promiseList).then((index) => {
                // 其中一張加載成功舞虱,刪除當(dāng)前promise欢际,讓PromiseList小于limit,開始遞歸矾兜,加載下一張
                console.log('end:' + index);
                loadIndexMap[index] = true;
                delete promiseMap[index];
                loadAImage();
            }).catch(e => {
                // 加載失敗也繼續(xù)
                console.log('end:' + e);
                loadIndexMap[e] = true;
                delete promiseMap[e];
                loadAImage();
            })
        }
    }
    loadAImage()
}

startLoadImage(urls, 3)

將代碼復(fù)制到chrome瀏覽器可以看到下面的運(yùn)行結(jié)果:

1.png

可以看到损趋,所有圖片加載完成,在沒有失敗的情況下椅寺,打印出來(lái)all浑槽。

解析:根據(jù)規(guī)則5,Promise.race方法接受的參數(shù)中有一個(gè)promise對(duì)象返回結(jié)果了就會(huì)立即觸發(fā)成功或者失敗的函數(shù)返帕。這里利用這個(gè)特性桐玻,先將promise隊(duì)列循環(huán)加入,直到達(dá)到限制荆萤,等待race镊靴,race后又加入一個(gè)promise,利用遞歸一直循環(huán)這個(gè)過(guò)程,到最后用promise.all捕獲剩下的圖片加載偏竟。

題目六

寫出下面函數(shù)的執(zhí)行結(jié)果:

Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)

根據(jù)規(guī)則4算行,Promise.resolve(1)會(huì)返回一個(gè)promise對(duì)象并且會(huì)將1當(dāng)做then的參數(shù)。而.then 或者 .catch 的參數(shù)期望是函數(shù)苫耸,傳入非函數(shù)則會(huì)發(fā)生值穿透州邢。所以最后會(huì)輸出:1。

題目六

如何取消一個(gè)promise?
剛開始拿到這個(gè)題會(huì)覺得比較蒙褪子,實(shí)際上量淌,我們可以用Promise,race的特點(diǎn),多個(gè)Promise有個(gè)狀態(tài)變?yōu)橥瓿上油剩蜁?huì)立馬返回呀枢。

function wrap(p) {
        let obj = {};
        let p1 = new Promise((resolve, reject) => {
          obj.resolve = resolve;
          obj.reject = reject;
        });
        obj.promise = Promise.race([p1, p]);
        return obj;
      }

      let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(123);
        }, 1000);
      });
      let obj = wrap(promise);
      obj.promise.then(res => {
        console.log(res);
      });
      // obj.resolve("請(qǐng)求被攔截了");

一旦開發(fā)者在1秒內(nèi)主動(dòng)調(diào)用obj.resolve,那么obj.promise方法就會(huì)被替換成我們自己的方法笼痛,而不會(huì)執(zhí)行let promisethen方法裙秋,實(shí)現(xiàn)上比較巧妙。

總結(jié)

promise對(duì)象在JavaScript中的使用相對(duì)復(fù)雜缨伊,因?yàn)閷懛ǘ嘧冋蹋异`活,提供的方法又比較復(fù)雜難懂刻坊,在ES6普及的今天枷恕,使用范圍也廣,所以會(huì)高頻的出現(xiàn)在面試過(guò)程中谭胚。

相關(guān)閱讀:

Promise講解

前端異步是什么徐块?哪些情況下會(huì)發(fā)生異步?

知道html5 Web Worker標(biāo)準(zhǔn)嗎灾而?能實(shí)現(xiàn)JavaScript的多線程胡控?

學(xué)習(xí)如逆水行舟,不進(jìn)則退旁趟,前端技術(shù)飛速發(fā)展昼激,如果每天不堅(jiān)持學(xué)習(xí),就會(huì)跟不上轻庆,我會(huì)陪著大家癣猾,每天堅(jiān)持推送博文,跟大家一同進(jìn)步余爆,希望大家能關(guān)注我,第一時(shí)間收到最新文章夸盟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛾方,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桩砰,老刑警劉巖拓春,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亚隅,居然都是意外死亡硼莽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門煮纵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)懂鸵,“玉大人,你說(shuō)我怎么就攤上這事行疏〈夜猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵酿联,是天一觀的道長(zhǎng)终息。 經(jīng)常有香客問我,道長(zhǎng)贞让,這世上最難降的妖魔是什么周崭? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喳张,結(jié)果婚禮上休傍,老公的妹妹穿的比我還像新娘。我一直安慰自己蹲姐,他們只是感情好磨取,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柴墩,像睡著了一般忙厌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上江咳,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天逢净,我揣著相機(jī)與錄音,去河邊找鬼歼指。 笑死爹土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的踩身。 我是一名探鬼主播胀茵,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挟阻!你這毒婦竟也來(lái)了琼娘?” 一聲冷哼從身側(cè)響起峭弟,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脱拼,沒想到半個(gè)月后瞒瘸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熄浓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年情臭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赌蔑。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俯在,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惯雳,到底是詐尸還是另有隱情朝巫,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布石景,位于F島的核電站劈猿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏潮孽。R本人自食惡果不足惜揪荣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望往史。 院中可真熱鬧仗颈,春花似錦、人聲如沸椎例。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)订歪。三九已至脖祈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刷晋,已是汗流浹背盖高。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眼虱,地道東北人喻奥。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像捏悬,于是被迫代替她去往敵國(guó)和親撞蚕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355