關於 ES6 中 Promise 的面試題

說明

最近在複習 Promise 的知識,因此就作了一些題,這裏挑出幾道題,你們一塊兒看看吧。html

題目一

const promise = new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
})

promise.then(() => {
    console.log(3);
})

console.log(4);
複製代碼

解析

首先 Promise 新建後當即執行,因此會先輸出 1,2,而 Promise.then() 內部的代碼在 當次 事件循環的 結尾 馬上執行 ,因此會繼續輸出4,最後輸出3。前端

答案

1
2
4
3
複製代碼

題目二

const promise = new Promise((resolve, reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});

promise.then((res) => {
    console.log('then:', res);
}).catch((err) => {
    console.log('catch:', err);
})
複製代碼

解析

resolve 函數將 Promise 對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;git

reject 函數將 Promise 對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。es6

而一旦狀態改變,就不會再變。
因此 代碼中的reject('error'); 不會有做用。github

Promise 只能 resolve 一次,剩下的調用都會被忽略。
因此 第二次的 resolve('success2'); 也不會有做用。面試

答案

then: success1
複製代碼

題目三

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
複製代碼

解析

Promise.resolve 方法的參數若是是一個原始值,或者是一個不具備 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態爲resolvedPromise.resolve 方法的參數,會同時傳給回調函數。ajax

then 方法接受的參數是函數,而若是傳遞的並不是是一個函數,它實際上會將其解釋爲 then(null),這就會致使前一個 Promise 的結果會穿透下面。數組

答案

1
複製代碼

題目四

紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個燈不斷交替重複亮燈?(用Promse實現)三個亮燈函數已經存在:promise

function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}
複製代碼

解析

紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次,意思就是3秒,執行一次 red 函數,2秒執行一次 green 函數,1秒執行一次 yellow 函數,不斷交替重複亮燈,意思就是按照這個順序一直執行這3個函數,這步能夠就利用遞歸來實現。併發

答案

function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}

var light = function (timmer, cb) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            cb();
            resolve();
        }, timmer);
    });
};

var step = function () {
    Promise.resolve().then(function () {
        return light(3000, red);
    }).then(function () {
        return light(2000, green);
    }).then(function () {
        return light(1000, yellow);
    }).then(function () {
        step();
    });
}

step();
複製代碼

題目五

實現 mergePromise 函數,把傳進去的數組按順序前後執行,而且把返回的數據前後放到數組 data 中。

const timeout = ms => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, ms);
});

const ajax1 = () => timeout(2000).then(() => {
    console.log('1');
    return 1;
});

const ajax2 = () => timeout(1000).then(() => {
    console.log('2');
    return 2;
});

const ajax3 = () => timeout(2000).then(() => {
    console.log('3');
    return 3;
});

const mergePromise = ajaxArray => {
    // 在這裏實現你的代碼

};

mergePromise([ajax1, ajax2, ajax3]).then(data => {
    console.log('done');
    console.log(data); // data 爲 [1, 2, 3]
});

// 要求分別輸出
// 1
// 2
// 3
// done
// [1, 2, 3]
複製代碼

解析

首先 ajax1 、ajax二、ajax3 都是函數,只是這些函數執行後會返回一個 Promise,按題目的要求咱們只要順序執行這三個函數就行了,而後把結果放到 data 中,可是這些函數裏都是異步操做,想要按順序執行,而後輸出 1,2,3並無那麼簡單,看個例子。

function A() {
    setTimeout(function () {
        console.log('a');
    }, 3000);
}

function B() {
    setTimeout(function () {
        console.log('b');
    }, 1000);
}

A();
B();

// b
// a
複製代碼

例子中咱們是按順序執行的 AB 可是輸出的結果倒是 ba 對於這些異步函數來講,並不會按順序執行完一個,再執行後一個。
這道題就是考用 Promise 控制異步流程,咱們要想辦法,讓這些函數,一個執行完以後,再執行下一個,看答案吧。

答案

// 保存數組中的函數執行後的結果
var data = [];

// Promise.resolve方法調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。
var sequence = Promise.resolve();

ajaxArray.forEach(function (item) {
    // 第一次的 then 方法用來執行數組中的每一個函數,
    // 第二次的 then 方法接受數組中的函數執行後返回的結果,
    // 並把結果添加到 data 中,而後把 data 返回。
    // 這裏對 sequence 的從新賦值,實際上是至關於延長了 Promise 鏈
    sequence = sequence.then(item).then(function (res) {
        data.push(res);
        return data;
    });
})

// 遍歷結束後,返回一個 Promise,也就是 sequence, 他的 [[PromiseValue]] 值就是 data,
// 而 data(保存數組中的函數執行後的結果) 也會做爲參數,傳入下次調用的 then 方法中。
return sequence;
複製代碼

題目六

如下代碼最後輸出什麼?

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });

}));

first().then((arg) => {
    console.log(arg);
});
console.log(4);
複製代碼

解析

這道題就其實和 Promise 的關係不太大,主要是須要理解 JS執行機制,才能很好的解決這道題,對於 JS 執行機制不瞭解的朋友推薦看看這篇文章

這一次,完全弄懂 JavaScript 執行機制

第一輪事件循環

先執行宏任務,主script ,new Promise當即執行,輸出【3】,
執行 p 這個new Promise 操做,輸出【7】,
發現 setTimeout,將回調放入下一輪任務隊列(Event Queue),p 的 then,姑且叫作 then1,放入微任務隊列,發現 first 的 then,叫 then2,放入微任務隊列。執行console.log(4),輸出【4】,宏任務執行結束。
再執行微任務,執行 then1,輸出【1】,
執行 then2,輸出【2】。
到此爲止,第一輪事件循環結束。開始執行第二輪。

第二輪事件循環

先執行宏任務裏面的,也就是 setTimeout 的回調,輸出【5】。
resolve(6) 不會生效,由於 p 這個 Promise 的狀態一旦改變就不會在改變了。

答案

3
7
4
1
2
5 
複製代碼

題目七

有 8 個圖片資源的 url,已經存儲在數組 urls 中(即urls = ['http://example.com/1.jpg', ...., 'http://example.com/8.jpg']),並且已經有一個函數 function loadImg,輸入一個 url 連接,返回一個 Promise,該 Promise 在圖片下載完成的時候 resolve,下載失敗則 reject。
可是咱們要求,任意時刻,同時下載的連接數量不能夠超過 3 個。
請寫一段代碼實現這個需求,要求儘量快速地將全部圖片下載完成。

var urls = ['https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 'https://www.kkkk1000.com/images/getImgData/gray.gif', 'https://www.kkkk1000.com/images/getImgData/Particle.gif', 'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 'https://www.kkkk1000.com/images/wxQrCode2.png'];
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function () {
            console.log('一張圖片加載完成');
            resolve();
        }
        img.onerror = reject
        img.src = url
    })
};
複製代碼

解析

題目的意思是須要咱們這麼作,先併發請求 3 張圖片,當一張圖片加載完成後,又會繼續發起一張圖片的請求,讓併發數保持在 3 個,直到須要加載的圖片都所有發起請求。

用 Promise 來實現就是,先併發請求3個圖片資源,這樣能夠獲得 3 個 Promise,組成一個數組,就叫promises 吧,而後不斷的調用 Promise.race 來返回最快改變狀態的 Promise,而後從數組(promises )中刪掉這個 Promise 對象,再加入一個新的 Promise,直到所有的 url 被取完,最後再使用 Promise.all 來處理一遍數組(promises )中沒有改變狀態的 Promise。

答案

var urls = ['https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 'https://www.kkkk1000.com/images/getImgData/gray.gif', 'https://www.kkkk1000.com/images/getImgData/Particle.gif', 'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 'https://www.kkkk1000.com/images/wxQrCode2.png'];
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function () {
            console.log('一張圖片加載完成');
            resolve();
        }
        img.onerror = reject
        img.src = url
    })
};

function limitLoad(urls, handler, limit) {
    // 對數組作一個拷貝
    const sequence = [].concat(urls)
    let promises = [];

    //併發請求到最大數
    promises = sequence.splice(0, limit).map((url, index) => {
        // 這裏返回的 index 是任務在 promises 的腳標,用於在 Promise.race 以後找到完成的任務腳標
        return handler(url).then(() => {
            return index
        }); 
    });

    // 利用數組的 reduce 方法來以隊列的形式執行
    return sequence.reduce((last, url, currentIndex) => {
        return last.then(() => {
            // 返回最快改變狀態的 Promise
            return Promise.race(promises)
        }).catch(err => {
            // 這裏的 catch 不只用來捕獲 前面 then 方法拋出的錯誤
            // 更重要的是防止中斷整個鏈式調用
            console.error(err)
        }).then((res) => {
            // 用新的 Promise 替換掉最快改變狀態的 Promise
            promises[res] = handler(sequence[currentIndex]).then(() => { return res });
        })
    }, Promise.resolve()).then(() => {
        return Promise.all(promises)
    })

}
limitLoad(urls, loadImg, 3)

/*
由於 limitLoad 函數也返回一個 Promise,因此當 全部圖片加載完成後,能夠繼續鏈式調用

limitLoad(urls, loadImg, 3).then(() => {
    console.log('全部圖片加載完成');
}).catch(err => {
    console.error(err);
})
*/
複製代碼

總結

這幾道題,有考查 Promise 基礎知識的,也有考對 Promise 靈活運用的,若是這些題你都作的很好的話,那你對 Promise 的理解應該是不錯的了。

最後,若是文中有不足或者錯誤的地方,還請小夥伴們指出,萬分感謝。
若是以爲文章說的內容不夠,最後有與題目相關的文章,能夠看看。

參考

ECMAScript 6 入門 —— 阮一峯

ES6 系列之咱們來聊聊 Promise

一道關於Promise應用的面試題

阿里前端測試題--關於ES6中Promise函數的理解與應用

這一次,完全弄懂 JavaScript 執行機制

一個Promise面試題

ES6原生Promise的全部方法介紹(附一道應用場景題目)

Promise 異步流程控制

相關文章
相關標籤/搜索