promise經典面試題

上期講了promise基本概念和用法,今天結合上期的內容,講解幾道經典的相關面試題。javascript

promise基本規則:

1. 首先Promise構造函數會當即執行,而Promise.then()內部的代碼在當次事件循環的結尾當即執行(微任務)。

2. promise的狀態一旦由等待pending變爲成功fulfilled或者失敗rejected。那麼當前promise被標記爲完成,後面則不會再次改變該狀態。

3. resolve函數和reject函數都將當前Promise狀態改成完成,並將異步結果,或者錯誤結果當作參數返回。

4. Promise.resolve(value)

返回一個狀態由給定 value 決定的 Promise 對象。若是該值是 thenable(即,帶有 then 方法的對象),返回的 Promise 對象的最終狀態由 then 方法執行決定;不然的話(該 value 爲空,基本類型或者不帶 then 方法的對象),返回的 Promise 對象狀態爲 fulfilled,而且將該 value 傳遞給對應的 then 方法。一般而言,若是你不知道一個值是不是 Promise 對象,使用 Promise.resolve(value) 來返回一個 Promise 對象,這樣就能將該 value 以 Promise 對象形式使用。html

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

簡單理解,這2個函數,是將接收到的promise列表的結果返回,區別是,all是等待全部的promise都觸發成功了,纔會返回,而arce有一個成功了就會返回結果。其中任何一個promise執行失敗了,都會直接返回失敗的結果。前端

6. promise對象的構造函數只會調用一次,then方法和catch方法都能屢次調用,但一旦有了肯定的結果,再次調用就會直接返回結果。

開始答題

題目一

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); 複製代碼

能夠看:規則一,promise構造函數的代碼會當即執行,then或者reject裏面的代碼會放入異步微任務隊列,在宏任務結束後會當即執行。規則二:promise的狀態一旦變動爲成功或者失敗,則不會再次改變,因此執行結果爲:1,2,4,3。而catch裏面的函數不會再執行。html5

題目二

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)  }) 複製代碼

根據規則6,promise的構造函數只會執行一次,而then方法能夠屢次調用,可是第二次是直接返回結果,不會有異步等待的時間,因此執行結果是: 過一秒打印:once,success,successjava

題目三

在瀏覽器上,下面的程序會一次輸出哪些內容?node

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); 複製代碼

事件循環:javascript的執行規則裏面有個事件循環Event Loot的規則,在事件循環中,異步事件會放到異步隊列裏面,可是異步隊列裏面又分爲宏任務和微任務,瀏覽器端的宏任務通常有:script標籤,setTimeout,setInterval,setImmediate,requestAnimationFrame。微任務有:MutationObserver,Promise.then catch finally。宏任務會阻塞瀏覽器的渲染進程,微任務會在宏任務結束後當即執行,在渲染以前。web

回到題目,結果爲:'1,2,10,5,6,8,9,3'。你答對了嗎?若是對了,那你基本理解了事件隊列,微任務,宏任務了。面試

第一步:執行宏任務,結合規則一,輸出:1,2,10。這時候事件循環裏面有異步任務timeOut1,timeOut2,p2.then,p1.thenchrome

第二步:宏任務執行完後Event Loop會去任務隊列取異步任務,微任務會優先執行,這時候會前後執行p2.then,p1.then,打印5,6。promise

第三步:微任務執行完了,開始宏任務,因爲2個settimeout等待時間同樣,因此會執行先進入異步隊列的timeOut2,前後打印:8。執行宏任務的過程當中,p3.then微任務進入了隊列,宏任務執行完畢會執行微任務,輸出:9。以後執行timeOut1,輸出:3。

第四步:結合規則6,因爲p2這個Promise對象的執行結果已經肯定,因此4不會被打印。

注:在node.js上輸出結果並非這樣的,由於node.js的事件循環跟瀏覽器端的有區別。

題目四

在不使用async/await的狀況下,順序執行一組異步代碼函數,並輸出最後的結果。

在上篇文章中,已經講到過,利用promise.resolve結合reduce能順序執行一組異步函數。

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)); 複製代碼

以上代碼能夠封裝成工具來使用,利用的是規則4,promise.resolve函數的特色,其中dd能夠是一組同步函數,也能夠是異步函數。最後的結果在result裏面,異常信息能在最後捕獲。想看更具體的能夠查看這篇文章: promise講解

題目五

順序加載10張圖片,圖片地址已知,可是同時最多加載3張圖片,要求用promise實現。

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) {  // 當前存在的promise隊列  let promiseMap = {};  // 當前索引對應的加載狀態,不管成功,失敗都會標記爲true,格式: {0: true, 1: true, 2: true...}  let loadIndexMap = {};  // 當前以及加載到的索引,方便找到下一個未加載的索引,爲了節省性能,其實能夠不要  let loadIndex = 0;  const loadAImage = function () {  // 全部的資源都進入了異步隊列  if (Object.keys(loadIndexMap).length === urls.length) {  // 全部的資源都加載完畢,或者進入加載狀態,遞歸結束  const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, [])  Promise.all(promiseList).then(res => {  // 這裏若是沒有加載失敗,就會在全部加載完畢後執行,若是其中某個錯誤了,這裏的結果就不許確,不過這個不是題目要求的。  console.log('all');  endHandle && endHandle()  }).catch((e) => {  console.log('end:' + e);  })  } else {  // 遍歷,知道里面有3個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;  }  }  }  // 獲取當前正在進行的promise列表,利用reduce從promiseMap裏面獲取  const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, [])  Promise.race(promiseList).then((index) => {  // 其中一張加載成功,刪除當前promise,讓PromiseList小於limit,開始遞歸,加載下一張  console.log('end:' + index);  loadIndexMap[index] = true;  delete promiseMap[index];  loadAImage();  }).catch(e => {  // 加載失敗也繼續  console.log('end:' + e);  loadIndexMap[e] = true;  delete promiseMap[e];  loadAImage();  })  }  }  loadAImage() }  startLoadImage(urls, 3) 複製代碼

將代碼複製到chrome瀏覽器能夠看到下面的運行結果: 1.png 能夠看到,全部圖片加載完成,在沒有失敗的狀況下,打印出來all

解析:根據規則5,Promise.race方法接受的參數中有一個promise對象返回結果了就會當即觸發成功或者失敗的函數。這裏利用這個特性,先將promise隊列循環加入,直到達到限制,等待racerace後又加入一個promise,利用遞歸一直循環這個過程,到最後用promise.all捕獲剩下的圖片加載。

題目六

寫出下面函數的執行結果:

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

根據規則4,Promise.resolve(1)會返回一個promise對象而且會將1當作then的參數。而.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。因此最後會輸出:1。

題目六

如何取消一個promise? 剛開始拿到這個題會以爲比較蒙,實際上,咱們能夠用Promise,race的特色,多個Promise有個狀態變爲完成,就會立馬返回。

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("請求被攔截了"); 複製代碼

一旦開發者在1秒內主動調用obj.resolve,那麼obj.promise方法就會被替換成咱們本身的方法,而不會執行let promisethen方法,實現上比較巧妙。

總結

promise對象在JavaScript中的使用相對複雜,由於寫法多變,並且靈活,提供的方法又比較複雜難懂,在ES6普及的今天,使用範圍也廣,因此會高頻的出如今面試過程當中。

相關閱讀:

Promise講解

前端異步是什麼?哪些狀況下會發生異步?

知道html5 Web Worker標準嗎?能實現JavaScript的多線程?

學習如逆水行舟,不進則退,前端技術飛速發展,若是天天不堅持學習,就會跟不上,我會陪着你們,天天堅持推送博文,跟你們一同進步,但願你們能關注我,第一時間收到最新文章。

我的公衆號:長按保存關注

相關文章
相關標籤/搜索