【建議星星】要就來45道Promise面試題一次爽到底(1.1w字用心整理)

前言

你盼世界,我盼望你無bug。Hello 你們好!我是霖呆呆!javascript

時隔一週不見,霖呆呆我終於更新文章了,小聲嘀咕說想我了...html

呸...前端

咳咳,其實我一直在隱忍準備來一發大的好不。java

這不,這一章節就是整理了45Promise的筆試題讓你們爽一爽 😁。es6

其實想要寫一篇關於Promise的文章是由於以前在寫別的文章的時候被評論區的一名讀者無情的嘲諷了👎:面試

"做者的Promise必定很爛"ajax

因此編寫這麼一個主題霖呆呆我不是爲了證實什麼,而是想說:segmentfault

"說我爛我能夠學啊"數組

另外查了不少關於Promise的面試題,有些一上來就很難的,有些連着幾篇題目都是同樣的,還有一些比較好的文章介紹的都是一些硬知識點。promise

這篇文章是一篇比較純的Promise筆試文章,是我本身在作題的時候,根據題目想要的考點來反敲知識點,而後再由這個知識點編寫從淺到深的的題目。

因此你能夠看到題目中有一些基礎題,而後再從基礎題慢慢的變難,若是你看着感受這段位配不上你的話,請答應我堅持看下去,會愈來愈難的...

咳咳,按部就班嘛...

本文的題目沒有到特別深刻,不過應該覆蓋了大部分的考點,另外爲了避免把你們繞混,答案也沒有考慮在Node的執行結果,執行結果全爲瀏覽器環境下。所以若是你都會作的話,能夠盡情的在評論區再給我一個👎,放心,我脾氣很好的...

OK👌, 來看看經過閱讀本篇文章你能夠學到:

  • Promise的幾道基礎題
  • Promise結合setTimeout
  • Promise中的then、catch、finally
  • Promise中的all和race
  • async/await的幾道題
  • async處理錯誤
  • 綜合題
  • 幾道大廠的面試題

前期準備

在作下面👇的題目以前,我但願你能清楚幾個知識點。

(若是你感受一上來不想看這些列舉的知識點的話,直接看後面的例子再來理解它們也能夠)

event loop它的執行順序:

  • 一開始整個腳本做爲一個宏任務執行
  • 執行過程當中同步代碼直接執行,宏任務進入宏任務隊列,微任務進入微任務隊列
  • 當前宏任務執行完出隊,檢查微任務列表,有則依次執行,直到所有執行完
  • 執行瀏覽器UI線程的渲染工做
  • 檢查是否有Web Worker任務,有則執行
  • 執行完本輪的宏任務,回到2,依此循環,直到宏任務和微任務隊列都爲空

微任務包括:MutationObserverPromise.then()或reject()Promise爲基礎開發的其它技術,好比fetch APIV8的垃圾回收過程、Node獨有的process.nextTick

宏任務包括scriptscriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

注意⚠️:在全部任務開始的時候,因爲宏任務中包括了script,因此瀏覽器會先執行一個宏任務,在這個過程當中你看到的延遲任務(例如setTimeout)將被放到下一輪宏任務中來執行。

1. Promise的幾道基礎題

1.1 題目一

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
})
console.log('1', promise1);
複製代碼

過程分析:

  • 從上至下,先遇到new Promise,執行該構造函數中的代碼promise1
  • 而後執行同步代碼1,此時promise1沒有被resolve或者reject,所以狀態仍是pending

結果:

'promise1'
'1' Promise{<pending>}
複製代碼

1.2 題目二

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
複製代碼

過程分析:

  • 從上至下,先遇到new Promise,執行其中的同步代碼1
  • 再遇到resolve('success'), 將promise的狀態改成了resolved而且將值保存下來
  • 繼續執行同步代碼2
  • 跳出promise,往下執行,碰到promise.then這個微任務,將其加入微任務隊列
  • 執行同步代碼4
  • 本輪宏任務所有執行完畢,檢查微任務隊列,發現promise.then這個微任務且狀態爲resolved,執行它。

結果:

1 2 4 3
複製代碼

1.3 題目三

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
複製代碼

過程分析

  • 和題目二類似,只不過在promise中並無resolve或者reject
  • 所以promise.then並不會執行,它只有在被改變了狀態以後纔會執行。

結果:

1 2 4
複製代碼

1.4 題目四

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
複製代碼

過程分析:

  • 從上至下,先遇到new Promise,執行該構造函數中的代碼promise1
  • 碰到resolve函數, 將promise1的狀態改變爲resolved, 並將結果保存下來
  • 碰到promise1.then這個微任務,將它放入微任務隊列
  • promise2是一個新的狀態爲pendingPromise
  • 執行同步代碼1, 同時打印出promise1的狀態是resolved
  • 執行同步代碼2,同時打印出promise2的狀態是pending
  • 宏任務執行完畢,查找微任務隊列,發現promise1.then這個微任務且狀態爲resolved,執行它。

結果:

'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
複製代碼

1.5 題目五

接下來看看這道題:

const fn = () => (new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
}))
fn().then(res => {
  console.log(res)
})
console.log('start')
複製代碼

這道題裏最早執行的是'start'嗎 🤔️ ?

請仔細看看哦,fn函數它是直接返回了一個new Promise的,並且fn函數的調用是在start以前,因此它裏面的內容應該會先執行。

結果:

1
'start'
'success'
複製代碼

1.6 題目六

若是把fn的調用放到start以後呢?

const fn = () =>
  new Promise((resolve, reject) => {
    console.log(1);
    resolve("success");
  });
console.log("start");
fn().then(res => {
  console.log(res);
});
複製代碼

是的,如今start就在1以前打印出來了,由於fn函數是以後執行的。

注意⚠️:以前咱們很容易就覺得看到new Promise()就執行它的第一個參數函數了,其實這是不對的,就像這兩道題中,咱們得注意它是否是被包裹在函數當中,若是是的話,只有在函數調用的時候纔會執行。

答案:

"start"
1
"success"
複製代碼

好嘞,學完了這幾道基礎題,讓咱們來用個表情包壓壓驚。

2. Promise結合setTimeout

2.1 題目一

console.log('start')
setTimeout(() => {
  console.log('time')
})
Promise.resolve().then(() => {
  console.log('resolve')
})
console.log('end')
複製代碼

過程分析:

  • 剛開始整個腳本做爲一個宏任務來執行,對於同步代碼直接壓入執行棧進行執行,所以先打印出startend
  • setTimout做爲一個宏任務被放入宏任務隊列(下一個)
  • Promise.then做爲一個微任務被放入微任務隊列
  • 本次宏任務執行完,檢查微任務,發現Promise.then,執行它
  • 接下來進入下一個宏任務,發現setTimeout,執行。

結果:

'start'
'end'
'resolve'
'time'
複製代碼

2.2 題目二

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);
複製代碼

過程分析:

和題目1.2很像,不過在resolve的外層加了一層setTimeout定時器。

  • 從上至下,先遇到new Promise,執行該構造函數中的代碼1
  • 而後碰到了定時器,將這個定時器中的函數放到下一個宏任務的延遲隊列中等待執行
  • 執行同步代碼2
  • 跳出promise函數,遇到promise.then,但其狀態仍是爲pending,這裏理解爲先不執行
  • 執行同步代碼4
  • 一輪循環事後,進入第二次宏任務,發現延遲隊列中有setTimeout定時器,執行它
  • 首先執行timerStart,而後遇到了resolve,將promise的狀態改成resolved且保存結果並將以前的promise.then推入微任務隊列
  • 繼續執行同步代碼timerEnd
  • 宏任務所有執行完畢,查找微任務隊列,發現promise.then這個微任務,執行它。

所以執行結果爲:

1
2
4
"timerStart"
"timerEnd"
"success"
複製代碼

2.3 題目三

題目三分了兩個題目,由於看着都差很少,不過執行的結果卻不同,你們不妨先猜猜下面兩個題目分別執行什麼:

(1):

setTimeout(() => {
  console.log('timer1');
  setTimeout(() => {
    console.log('timer3')
  }, 0)
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
複製代碼

(2):

setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
複製代碼

執行結果:

'start'
'timer1'
'timer2'
'timer3'
複製代碼
'start'
'timer1'
'promise'
'timer2'
複製代碼

這兩個例子,看着好像只是把第一個定時器中的內容換了一下而已。

一個是爲定時器timer3,一個是爲Promise.then

可是若是是定時器timer3的話,它會在timer2後執行,而Promise.then倒是在timer2以前執行。

你能夠這樣理解,Promise.then是微任務,它會被加入到本輪中的微任務列表,而定時器timer3是宏任務,它會被加入到下一輪的宏任務中。

理解完這兩個案例,能夠來看看下面一道比較難的題目了。

2.3 題目三

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');
複製代碼

這道題稍微的難一些,在promise中執行定時器,又在定時器中執行promise

而且要注意的是,這裏的Promise是直接resolve的,而以前的new Promise不同。

所以過程分析爲:

  • 剛開始整個腳本做爲第一次宏任務來執行,咱們將它標記爲宏1,從上至下執行
  • 遇到Promise.resolve().then這個微任務,將then中的內容加入第一次的微任務隊列標記爲微1
  • 遇到定時器timer1,將它加入下一次宏任務的延遲列表,標記爲宏2,等待執行(先無論裏面是什麼內容)
  • 執行宏1中的同步代碼start
  • 第一次宏任務(宏1)執行完畢,檢查第一次的微任務隊列(微1),發現有一個promise.then這個微任務須要執行
  • 執行打印出微1中同步代碼promise1,而後發現定時器timer2,將它加入宏2的後面,標記爲宏3
  • 第一次微任務隊列(微1)執行完畢,執行第二次宏任務(宏2),首先執行同步代碼timer1
  • 而後遇到了promise2這個微任務,將它加入這次循環的微任務隊列,標記爲微2
  • 宏2中沒有同步代碼可執行了,查找本次循環的微任務隊列(微2),發現了promise2,執行它
  • 第二輪執行完畢,執行宏3,打印出timer2

因此結果爲:

'start'
'promise1'
'timer1'
'promise2'
'timer2'
複製代碼

若是感受有點繞的話,能夠看下面這張圖,就一目瞭然了。

2.4 題目四

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)
複製代碼

過程分析:

  • 從上至下,先執行第一個new Promise中的函數,碰到setTimeout將它加入下一個宏任務列表
  • 跳出new Promise,碰到promise1.then這個微任務,但其狀態仍是爲pending,這裏理解爲先不執行
  • promise2是一個新的狀態爲pendingPromise
  • 執行同步代碼console.log('promise1'),且打印出的promise1的狀態爲pending
  • 執行同步代碼console.log('promise2'),且打印出的promise2的狀態爲pending
  • 碰到第二個定時器,將其放入下一個宏任務列表
  • 第一輪宏任務執行結束,而且沒有微任務須要執行,所以執行第二輪宏任務
  • 先執行第一個定時器裏的內容,將promise1的狀態改成resolved且保存結果並將以前的promise1.then推入微任務隊列
  • 該定時器中沒有其它的同步代碼可執行,所以執行本輪的微任務隊列,也就是promise1.then,它拋出了一個錯誤,且將promise2的狀態設置爲了rejected
  • 第一個定時器執行完畢,開始執行第二個定時器中的內容
  • 打印出'promise1',且此時promise1的狀態爲resolved
  • 打印出'promise2',且此時promise2的狀態爲rejected

完整的結果爲:

'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}
複製代碼

2.5 題目五

若是你上面這道題搞懂了以後,咱們就能夠來作作這道了,你應該能很快就給出答案:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
    console.log("timer1");
  }, 1000);
  console.log("promise1裏的內容");
});
const promise2 = promise1.then(() => {
  throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
  console.log("timer2");
  console.log("promise1", promise1);
  console.log("promise2", promise2);
}, 2000);
複製代碼

結果:

'promise1裏的內容'
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
'timer1'
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'timer2'
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}
複製代碼

3. Promise中的then、catch、finally

額,可能你看到下面👇這麼多的1,2,3脾氣就上來了,不是說好了本篇文章沒什麼屁話嘛,怎麼仍是這麼多一二三四。

😂,你要理解個人用心良苦啊,我這是幫你把知識點都列舉出來,作個總結而已。固然,你也能夠先不看,先去作後面的題,而後再回過頭來看這些,你就以爲這些點都好好懂啊,甚至都不須要記。

總結:

  1. Promise的狀態一經改變就不能再改變。(見3.1)
  2. .then.catch都會返回一個新的Promise。(上面的👆1.4證實了)
  3. catch無論被鏈接到哪裏,都能捕獲上層的錯誤。(見3.2)
  4. Promise中,返回任意一個非 promise 的值都會被包裹成 promise 對象,例如return 2會被包裝爲return Promise.resolve(2)
  5. Promise.then 或者 .catch 能夠被調用屢次, 當若是Promise內部的狀態一經改變,而且有了一個值,那麼後續每次調用.then或者.catch的時候都會直接拿到該值。(見3.5)
  6. .then 或者 .catchreturn 一個 error 對象並不會拋出錯誤,因此不會被後續的 .catch 捕獲。(見3.6)
  7. .then.catch 返回的值不能是 promise 自己,不然會形成死循環。(見3.7)
  8. .then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。(見3.8)
  9. .then方法是能接收兩個參數的,第一個是處理成功的函數,第二個是處理失敗的函數,再某些時候你能夠認爲catch.then第二個參數的簡便寫法。(見3.9)
  10. .finally方法也是返回一個Promise,他在Promise結束的時候,不管結果爲resolved仍是rejected,都會執行裏面的回調函數。

3.1 題目一

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

結果:

"then: success1"
複製代碼

構造函數中的 resolvereject 只有第一次執行有效,屢次調用沒有任何做用 。驗證了第一個結論,Promise的狀態一經改變就不能再改變。

3.2 題目二

const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then: ", res);
  }).then(res => {
    console.log("then: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  }).then(res => {
    console.log("then: ", res);
  })
複製代碼

結果:

"catch: " "error"
"then3: " undefined
複製代碼

驗證了第三個結論,catch無論被鏈接到哪裏,都能捕獲上層的錯誤。

3.3 題目三

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
複製代碼

結果:

1
2
複製代碼

Promise能夠鏈式調用,不過promise 每次調用 .then 或者 .catch 都會返回一個新的 promise,從而實現了鏈式調用, 它並不像通常咱們任務的鏈式調用同樣return this

上面的輸出結果之因此依次打印出12,那是由於resolve(1)以後走的是第一個then方法,並無走catch裏,因此第二個then中的res獲得的其實是第一個then的返回值。

return 2會被包裝成resolve(2)

3.4 題目四

若是把3.3中的Promise.resolve(1)改成Promise.reject(1)又會怎麼樣呢?

Promise.reject(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    console.log(err);
    return 3
  })
  .then(res => {
    console.log(res);
  });
複製代碼

結果:

1
3
複製代碼

結果打印的固然是 1 和 3啦,由於reject(1)此時走的就是catch,且第二個then中的res獲得的就是catch中的返回值。

3.5 題目五

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('timer')
    resolve('success')
  }, 1000)
})
const start = Date.now();
promise.then(res => {
  console.log(res, Date.now() - start)
})
promise.then(res => {
  console.log(res, Date.now() - start)
})
複製代碼

執行結果:

'timer'
success 1001
success 1002
複製代碼

固然,若是你足夠快的話,也可能兩個都是1001

Promise.then 或者 .catch 能夠被調用屢次,但這裏 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值。

3.6 題目六

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})
複製代碼

猜猜這裏的結果輸出的是什麼 🤔️ ?

你可能想到的是進入.catch而後被捕獲了錯誤。

結果並非這樣的,它走的是.then裏面:

"then: " "Error: error!!!"
複製代碼

這也驗證了第4點和第6點,返回任意一個非 promise 的值都會被包裹成 promise 對象,所以這裏的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))

固然若是你拋出一個錯誤的話,能夠用下面👇兩的任意一種:

return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')
複製代碼

3.7 題目七

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
複製代碼

.then.catch 返回的值不能是 promise 自己,不然會形成死循環。

所以結果會報錯:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
複製代碼

3.8 題目八

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

這道題看着好像很簡單,又感受很複雜的樣子,怎麼這麼多個.then啊... 😅

其實你只要記住原則8.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。

第一個then和第二個then中傳入的都不是函數,一個是數字類型,一個是對象類型,所以發生了穿透,將resolve(1) 的值直接傳到最後一個then裏。

因此輸出結果爲:

1
複製代碼

3.9 題目九

下面來介紹一下.then函數中的兩個參數。

第一個參數是用來處理Promise成功的函數,第二個則是處理失敗的函數。

也就是說Promise.resolve('1')的值會進入成功的函數,Promise.reject('2')的值會進入失敗的函數。

讓咱們來看看這個例子🌰:

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
複製代碼

這裏的執行結果是:

'error' 'error!!!'
複製代碼

它進入的是then()中的第二個參數裏面,而若是把第二個參數去掉,就進入了catch()中:

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }).catch(err => {
    console.log('catch', err)
  })
複製代碼

執行結果:

'catch' 'error!!!'
複製代碼

可是有一個問題,若是是這個案例呢?

Promise.resolve()
  .then(function success (res) {
    throw new Error('error!!!')
  }, function fail1 (err) {
    console.log('fail1', err)
  }).catch(function fail2 (err) {
    console.log('fail2', err)
  })
複製代碼

因爲Promise調用的是resolve(),所以.then()執行的應該是success()函數,但是success()函數拋出的是一個錯誤,它會被後面的catch()給捕獲到,而不是被fail1函數捕獲。

所以執行結果爲:

fail2 Error: error!!!
			at success
複製代碼

3.10 題目十

接着來看看.finally(),這個功能通常不太用在面試中,不過若是碰到了你也應該知道該如何處理。

function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
複製代碼

結果:

'promise1'
'1'
'error'
'finally1'
'finally2'
複製代碼

4. Promise中的all和race

在作下面👇的題目以前,讓咱們先來了解一下Promise.all()Promise.race()的用法。

通俗來講,.all()的做用是接收一組異步任務,而後並行執行異步任務,而且在全部異步操做執行完後才執行回調。

.race()的做用也是接收一組異步任務,而後並行執行異步任務,只保留取第一個執行完成的異步操做的結果,其餘的方法仍在執行,不過執行結果會被拋棄。

來看看題目一。

4.1 題目一

咱們知道若是直接在腳本文件中定義一個Promise,它構造函數的第一個參數是會當即執行的,就像這樣:

const p1 = new Promise(r => console.log('當即打印'))
複製代碼

控制檯中會當即打印出 「當即打印」。

所以爲了控制它何時執行,咱們能夠用一個函數包裹着它,在須要它執行的時候,調用這個函數就能夠了:

function runP1 () {
	const p1 = new Promise(r => console.log('當即打印'))
	return p1
}

runP1() // 調用此函數時才執行
複製代碼

OK 👌, 讓咱們迴歸正題。

如今來構建這麼一個函數:

function runAsync (x) {
	const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
	return p
}
複製代碼

該函數傳入一個值x,而後間隔一秒後打印出這個x

若是我用.all()來執行它會怎樣呢?

function runAsync (x) {
	const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
	return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log(res))
複製代碼

先來想一想此段代碼在瀏覽器中會如何執行?

沒錯,當你打開頁面的時候,在間隔一秒後,控制檯會同時打印出1, 2, 3,還有一個數組[1, 2, 3]

1
2
3
[1, 2, 3]
複製代碼

因此你如今能理解這句話的意思了嗎:有了all,你就能夠並行執行多個異步操做,而且在一個回調中處理全部的返回數據。

.all()後面的.then()裏的回調函數接收的就是全部異步操做的結果。

並且這個結果中數組的順序和Promise.all()接收到的數組順序一致!!!

有一個場景是很適合用這個的,一些遊戲類的素材比較多的應用,打開網頁時,預先加載須要用到的各類資源如圖片、flash以及各類靜態文件。全部的都加載完後,咱們再進行頁面的初始化。

4.2 題目二

我新增了一個runReject函數,它用來在1000 * x秒後reject一個錯誤。

同時.catch()函數可以捕獲到.all()裏最早的那個異常,而且只執行一次。

想一想這道題會怎樣執行呢 🤔️?

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then(res => console.log(res))
  .catch(err => console.log(err))
複製代碼

不賣關子了 😁,讓我來公佈答案:

1
3
// 2s後輸出
2
Error: 2
// 4s後輸出
4
複製代碼

沒錯,就像我以前說的,.catch是會捕獲最早的那個異常,在這道題目中最早的異常就是runReject(2)的結果。

另外,若是一組異步操做中有一個異常都不會進入.then()的第一個回調函數參數中。

注意,爲何不說是不進入.then()中呢 🤔️?

哈哈,你們別忘了.then()方法的第二個參數也是能夠捕獲錯誤的:

Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then(res => console.log(res), 
  err => console.log(err))
複製代碼

4.3 題目三

接下來讓咱們看看另外一個有趣的方法.race

讓我看看大家的英語水平如何?

快!一秒鐘告訴我race是什麼意思?

好吧...大家果真很強...

race,比賽,賽跑的意思。

因此使用.race()方法,它只會獲取最早執行完成的那個結果,其它的異步任務雖然也會繼續進行下去,不過race已經無論那些任務的結果了。

來,改造一下4.1這道題:

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log('result: ', res))
  .catch(err => console.log(err))
複製代碼

執行結果爲:

1
'result: ' 1
2
3
複製代碼

這個race有什麼用呢?使用場景仍是不少的,好比咱們能夠用race給某個異步請求設置超時時間,而且在超時後執行相應的操做

4.4 題目四

改造一下題目4.2

function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));
複製代碼

遇到錯誤的話,也是同樣的,在這道題中,runReject(0)最早執行完,因此進入了catch()中:

0
'Error: 0'
1
2
3
複製代碼

總結

好的,讓咱們來總結一下.then().race()吧,😄

  • Promise.all()的做用是接收一組異步任務,而後並行執行異步任務,而且在全部異步操做執行完後才執行回調。
  • .race()的做用也是接收一組異步任務,而後並行執行異步任務,只保留取第一個執行完成的異步操做的結果,其餘的方法仍在執行,不過執行結果會被拋棄。
  • Promise.all().then()結果中數組的順序和Promise.all()接收到的數組順序一致。

5. async/await的幾道題

既然談到了Promise,那就確定得再說說async/await,在不少時候asyncPromise的解法差很少,又有些不同。不信你來看看題目一。

5.1 題目一

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')
複製代碼

這道基礎題輸出的是啥?

答案:

'async1 start'
'async2'
'start'
'async1 end'
複製代碼

過程分析:

  • 首先一進來是建立了兩個函數的,咱們先不看函數的建立位置,而是看它的調用位置
  • 發現async1函數被調用了,而後去看看調用的內容
  • 執行函數中的同步代碼async1 start,以後碰到了await,它會阻塞async1後面代碼的執行,所以會先去執行async2中的同步代碼async2,而後跳出async1
  • 跳出async1函數後,執行同步代碼start
  • 在一輪宏任務所有執行完以後,再來執行剛剛await後面的內容async1 end

(在這裏,你能夠理解爲await後面的內容就至關於放到了Promise.then的裏面)

來看看區別,若是咱們把await async2()換成一個new Promise呢?

async function async1() {
  console.log("async1 start");
  new Promise(resolve => {
    console.log('promise')
  })
  console.log("async1 end");
}
async1();
console.log("start")
複製代碼

此時的執行結果爲:

'async start'
'promise'
'async1 end'
'start'
複製代碼

能夠看到new Promise()並不會阻塞後面的同步代碼async1 end的執行。

5.2 題目二

如今將async結合定時器看看。

給題目一中的 async2函數中加上一個定時器:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  setTimeout(() => {
    console.log('timer')
  }, 0)
  console.log("async2");
}
async1();
console.log("start")
複製代碼

沒錯,定時器始終仍是最後執行的,它被放到下一條宏任務的延遲隊列中。

答案:

'async1 start'
'async2'
'start'
'async1 end'
'timer'
複製代碼

5.3 題目三

來吧,小夥伴們,讓咱們多加幾個定時器看看。😁

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")
複製代碼

思考一下🤔,執行結果會是什麼?

其實若是你能作到這裏了,說明你前面的那些知識點也都掌握了,我就不須要太過詳細的步驟分析了。

直接公佈答案吧:

'async1 start'
'async2'
'start'
'async1 end'
'timer2'
'timer3'
'timer1'
複製代碼

定時器誰先執行,你只須要關注誰先被調用的以及延遲時間是多少,這道題中延遲時間都是0,因此只要關注誰先被調用的。。

5.4 題目四

正常狀況下,async中的await命令是一個Promise對象,返回該對象的結果。

但若是不是Promise對象的話,就會直接返回對應的值,至關於Promise.resolve()

async function fn () {
  // return await 1234
  // 等同於
  return 123
}
fn().then(res => console.log(res))
複製代碼

結果:

123
複製代碼

5.5 題目五

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
複製代碼

這道題目比較有意思,你們要注意了。

async1await後面的Promise是沒有返回值的,也就是它的狀態始終是pending狀態,所以至關於一直在awaitawaitawait卻始終沒有響應...

因此在await以後的內容是不會執行的,也包括async1後面的 .then

答案爲:

'script start'
'async1 start'
'promise1'
'script end'
複製代碼

5.6 題目六

讓咱們給5.5中的Promise加上resolve

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise1 resolve')
  }).then(res => console.log(res))
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
複製代碼

如今Promise有了返回值了,所以await後面的內容將會被執行:

'script start'
'async1 start'
'promise1'
'script end'
'promise1 resolve'
'async1 success'
'async1 end'
複製代碼

5.7 題目七

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise resolve')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => {
  console.log(res)
})
new Promise(resolve => {
  console.log('promise2')
  setTimeout(() => {
    console.log('timer')
  })
})
複製代碼

這道題應該也不難,不過有一點須要注意的,在async1中的new Promise它的resovle的值和async1().then()裏的值是沒有關係的,不少小夥伴可能看到resovle('promise resolve')就會誤覺得是async1().then()中的返回值。

所以這裏的執行結果爲:

'script start'
'async1 start'
'promise1'
'promise2'
'async1 success'
'sync1 end'
'timer'
複製代碼

5.8 題目八

咱們再來看一道頭條曾經的面試題:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}

async function async2() {
  console.log("async2");
}

console.log("script start");

setTimeout(function() {
  console.log("setTimeout");
}, 0);

async1();

new Promise(function(resolve) {
  console.log("promise1");
  resolve();
}).then(function() {
  console.log("promise2");
});
console.log('script end')
複製代碼

有了上面👆幾題作基礎,相信你很快也能答上來了。

自信的寫下大家的答案吧。

'script start'
'async1 start'
'async2'
'promise1'
'script end'
'async1 end'
'promise2'
'setTimeout'
複製代碼

(這道題最後async1 endpromise2的順序其實在網上飽受爭議,我這裏使用瀏覽器Chrome V80Node v12.16.1的執行結果都是上面這個答案)

5.9 題目九

好的👌,async/await大法已練成,我們繼續:

async function testSometing() {
  console.log("執行testSometing");
  return "testSometing";
}

async function testAsync() {
  console.log("執行testAsync");
  return Promise.resolve("hello async");
}

async function test() {
  console.log("test start...");
  const v1 = await testSometing();
  console.log(v1);
  const v2 = await testAsync();
  console.log(v2);
  console.log(v1, v2);
}

test();

var promise = new Promise(resolve => {
  console.log("promise start...");
  resolve("promise");
});
promise.then(val => console.log(val));

console.log("test end...");
複製代碼

答案:

'test start...'
'執行testSometing'
'promise start...'
'test end...'
'testSometing'
'執行testAsync'
'promise'
'hello async'
'testSometing' 'hello async'
複製代碼

6. async處理錯誤

6.1 題目一

async中,若是 await後面的內容是一個異常或者錯誤的話,會怎樣呢?

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))
複製代碼

例如這道題中,await後面跟着的是一個狀態爲rejectedpromise

若是在async函數中拋出了錯誤,則終止錯誤結果,不會繼續向下執行。

因此答案爲:

'async2'
Uncaught (in promise) error
複製代碼

若是改成throw new Error也是同樣的:

async function async1 () {
  console.log('async1');
  throw new Error('error!!!')
  return 'async1 success'
}
async1().then(res => console.log(res))
複製代碼

結果爲:

'async1'
Uncaught (in promise) Error: error!!!
複製代碼

6.2 題目二

若是想要使得錯誤的地方不影響async函數後續的執行的話,可使用try catch

async function async1 () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    console.log(e)
  }
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')
複製代碼

這裏的結果爲:

'script start'
'error!!!'
'async1'
'async1 success'
複製代碼

或者你能夠直接在Promise.reject後面跟着一個catch()方法:

async function async1 () {
  // try {
  // await Promise.reject('error!!!')
  // } catch(e) {
  // console.log(e)
  // }
  await Promise.reject('error!!!')
    .catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')
複製代碼

運行結果是同樣的。

7. 綜合題

上面👆的題目都是被我拆分着說一些功能點,如今讓咱們來作一些比較難的綜合題吧。

7.1 題目一

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

}));

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

過程分析:

  • 第一段代碼定義的是一個函數,因此咱們得看看它是在哪執行的,發現它在4以前,因此能夠來看看first函數裏面的內容了。(這一步有點相似於題目1.5)
  • 函數first返回的是一個new Promise(),所以先執行裏面的同步代碼3
  • 接着又遇到了一個new Promise(),直接執行裏面的同步代碼7
  • 執行完7以後,在p中,遇到了一個定時器,先將它放到下一個宏任務隊列裏無論它,接着向下走
  • 碰到了resolve(1),這裏就把p的狀態改成了resolved,且返回值爲1,不過這裏也先不執行
  • 跳出p,碰到了resolve(2),這裏的resolve(2),表示的是把first函數返回的那個Promise的狀態改了,也先無論它。
  • 而後碰到了p.then,將它加入本次循環的微任務列表,等待執行
  • 跳出first函數,遇到了first().then(),將它加入本次循環的微任務列表(p.then的後面執行)
  • 而後執行同步代碼4
  • 本輪的同步代碼所有執行完畢,查找微任務列表,發現p.thenfirst().then(),依次執行,打印出1和2
  • 本輪任務執行完畢了,發現還有一個定時器沒有跑完,接着執行這個定時器裏的內容,執行同步代碼5
  • 而後又遇到了一個resolve(6),它是放在p裏的,可是p的狀態在以前已經發生過改變了,所以這裏就不會再改變,也就是說resolve(6)至關於沒任何用處,所以打印出來的pPromise{<resolved>: 1}。(這一步相似於題目3.1)

結果:

3
7
4
1
2
5
Promise{<resolved>: 1}
複製代碼

作對了的小夥伴獎勵本身一朵小(大)(嘴)(巴)吧,😄

7.2 題目二

const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)
複製代碼

注意的知識點:

  • async函數中awaitnew Promise要是沒有返回值的話則不執行後面的內容(相似題5.5)
  • .then函數中的參數期待的是函數,若是不是函數的話會發生穿透(相似題3.8 )
  • 注意定時器的延遲時間

所以本題答案爲:

'script start'
'async1'
'promise1'
'script end'
1
'timer2'
'timer1'
複製代碼

7.3 題目三

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resovle1');
  resolve('resolve2');
}).then(res => {
  console.log(res)
  setTimeout(() => {
    console.log(p1)
  }, 1000)
}).finally(res => {
  console.log('finally', res)
})
複製代碼

注意的知識點:

  • Promise的狀態一旦改變就沒法改變(相似題目3.5)
  • finally無論Promise的狀態是resolved仍是rejected都會執行,且它的回調函數是沒有參數的(相似3.10)

答案:

'resolve1'
'finally' undefined
'timer1'
Promise{<resolved>: undefined}
複製代碼

8. 幾道大廠的面試題

8.1 使用Promise實現每隔1秒輸出1,2,3

這道題比較簡單的一種作法是能夠用Promise配合着reduce不停的在promise後面疊加.then,請看下面的代碼:

const arr = [1, 2, 3]
arr.reduce((p, x) => {
  return p.then(() => {
    return new Promise(r => {
      setTimeout(() => r(console.log(x)), 1000)
    })
  })
}, Promise.resolve())
複製代碼

或者你能夠更簡單一點寫:

const arr = [1, 2, 3]
arr.reduce((p, x) => p.then(() => new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve())
複製代碼

參考連接:如何讓異步操做順序執行

8.2 使用Promise實現紅綠燈交替重複亮

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

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

答案:

function red() {
  console.log("red");
}
function green() {
  console.log("green");
}
function yellow() {
  console.log("yellow");
}
const light = function (timer, cb) {
  return new Promise(resolve => {
    setTimeout(() => {
      cb()
      resolve()
    }, timer)
  })
}
const step = function () {
  Promise.resolve().then(() => {
    return light(3000, red)
  }).then(() => {
    return light(2000, green)
  }).then(() => {
    return light(1000, yellow)
  }).then(() => {
    return step()
  })
}

step();
複製代碼

8.3 實現mergePromise函數

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

const time = (timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timer)
  })
}
const ajax1 = () => time(2000).then(() => {
  console.log(1);
  return 1
})
const ajax2 = () => time(1000).then(() => {
  console.log(2);
  return 2
})
const ajax3 = () => time(1000).then(() => {
  console.log(3);
  return 3
})

function mergePromise () {
  // 在這裏寫代碼
}

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

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

這道題有點相似於Promise.all(),不過.all()不須要管執行順序,只須要併發執行就好了。可是這裏須要等上一個執行完畢以後才能執行下一個。

解題思路:

  • 定義一個數組data用於保存全部異步操做的結果
  • 初始化一個const promise = Promise.resolve(),而後循環遍歷數組,在promise後面添加執行ajax任務,同時要將添加的結果從新賦值到promise上。

答案:

function mergePromise (ajaxArray) {
  // 存放每一個ajax的結果
  const data = [];
  let promise = Promise.resolve();
  ajaxArray.forEach(ajax => {
  	// 第一次的then爲了用來調用ajax
  	// 第二次的then是爲了獲取ajax的結果
    promise = promise.then(ajax).then(res => {
      data.push(res);
      return data; // 把每次的結果返回
    })
  })
  // 最後獲得的promise它的值就是data
  return promise;
}
複製代碼

8.4 根據promiseA+實現一個本身的promise

說真的,這道題被問到的機率仍是挺高的,並且要說的內容也不少...

霖呆呆這裏偷個懶,不想細說了...

不過哈,我保證,下下題我必定仔細說 😼.

來吧,給大家一些好的寶典:

8.5 封裝一個異步加載圖片的方法

這個相對簡單一些,只須要在圖片的onload函數中,使用resolve返回一下就能夠了。

來看看具體代碼:

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一張圖片加載完成");
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });
複製代碼

8.6 限制異步操做的併發個數並儘量快的完成所有

有8個圖片資源的url,已經存儲在數組urls中。

urls相似於['https://image1.png', 'https://image2.png', ....]

並且已經有一個函數function loadImg,輸入一個url連接,返回一個Promise,該Promise在圖片下載完成的時候resolve,下載失敗則reject

但有一個要求,任什麼時候刻同時下載的連接數量不能夠超過3個

請寫一段代碼實現這個需求,要求儘量快速地將全部圖片下載完成。

var urls = [
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一張圖片加載完成");
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });
複製代碼

看到這道題時,我最開始的想法是:

  • 拿到urls,而後將這個數組每3個url一組建立成一個二維數組
  • 而後用Promise.all()每次加載一組url(也就是併發3個),這一組加載完再加載下一組。

這個想法從技術上說並不難實現,有點相似於第三題。不過缺點也明顯,那就是每次都要等到上一組所有加載完以後,才加載下一組,那若是上一組有2個已經加載完了,還有1個特別慢,還在加載,要等這個慢的也加載完才能進入下一組。這明顯會照常卡頓,影響加載效率。

可是開始沒有考慮這麼多,所以有了第一個版本。

若是你有興趣能夠看看想法一的代碼,雖然對你沒什麼幫助,想直接知道比較好的作法的小夥伴請跳到想法二

想法一💡:

function limitLoad (urls, handler, limit) {
  const data = []; // 存儲全部的加載結果
  let p = Promise.resolve();
  const handleUrls = (urls) => { // 這個函數是爲了生成3個url爲一組的二維數組
    const doubleDim = [];
    const len = Math.ceil(urls.length / limit); // Math.ceil(8 / 3) = 3
    console.log(len) // 3, 表示二維數組的長度爲3
    for (let i = 0; i < len; i++) {
      doubleDim.push(urls.slice(i * limit, (i + 1) * limit))
    }
    return doubleDim;
  }
  const ajaxImage = (urlCollect) => { // 將一組字符串url 轉換爲一個加載圖片的數組
    console.log(urlCollect)
    return urlCollect.map(url => handler(url))
  }
  const doubleDim = handleUrls(urls); // 獲得3個url爲一組的二維數組
  doubleDim.forEach(urlCollect => {
    p = p.then(() => Promise.all(ajaxImage(urlCollect))).then(res => {
      data.push(...res); // 將每次的結果展開,並存儲到data中 (res爲:[img, img, img])
      return data;
    })
  })
  return p;
}
limitLoad(urls, loadImg, 3).then(res => {
  console.log(res); // 最終獲得的是長度爲8的img數組: [img, img, img, ...]
  res.forEach(img => {
    document.body.appendChild(img);
  })
});
複製代碼

想法二💡:

參考LHH大翰仔仔-Promise面試題

既然題目的要求是保證每次併發請求的數量爲3,那麼咱們能夠先請求urls中的前面三個(下標爲0,1,2),而且請求的時候使用Promise.race()來同時請求,三個中有一個先完成了(例以下標爲1的圖片),咱們就把這個當前數組中已經完成的那一項(第1項)換成尚未請求的那一項(urls中下標爲3)。

直到urls已經遍歷完了,而後將最後三個沒有完成的請求(也就是狀態沒有改變的Promise)用Promise.all()來加載它們。

很少說,流程圖都給你畫好了,你能夠結合流程圖再來看代碼。

爲了方便你查看,我截了個圖,不過代碼在後面也有

(說真的,要我看這一大長串代碼我也不肯意...)

代碼:

function limitLoad(urls, handler, limit) {
  let sequence = [].concat(urls); // 複製urls
  // 這一步是爲了初始化 promises 這個"容器"
  let promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => {
      // 返回下標是爲了知道數組中是哪一項最早完成
      return index;
    });
  });
  // 注意這裏要將整個變量過程返回,這樣獲得的就是一個Promise,能夠在外面鏈式調用
  return sequence
    .reduce((pCollect, url) => {
      return pCollect
        .then(() => {
          return Promise.race(promises); // 返回已經完成的下標
        })
        .then(fastestIndex => { // 獲取到已經完成的下標
        	// 將"容器"內已經完成的那一項替換
          promises[fastestIndex] = handler(url).then(
            () => {
              return fastestIndex; // 要繼續將這個下標返回,以便下一次變量
            }
          );
        })
        .catch(err => {
          console.error(err);
        });
    }, Promise.resolve()) // 初始化傳入
    .then(() => { // 最後三個用.all來調用
      return Promise.all(promises);
    });
}
limitLoad(urls, loadImg, 3)
  .then(res => {
    console.log("圖片所有加載完畢");
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });
複製代碼

後語

知識無價,支持原創。

參考文章:

你盼世界, 我盼望你無bug。這篇文章就介紹到這裏,一口氣刷完了45道題,真的很爽有沒有...

反正我作到後面是愈來愈有勁,也愈來愈自信了(有點飄,收一下...)

喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai 或者掃一掃下面的二維碼👇👇👇.

我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉

你的鼓勵就是我持續創做的主要動力 😊.

相關推薦:

《全網最詳bpmn.js教材》

《JavaScript進階-執行上下文(理解執行上下文一篇就夠了)》

《霖呆呆你來講說瀏覽器緩存吧》

《建議改爲: 讀完這篇你還不懂Babel我給你寄口罩》

《怎樣讓後臺小哥哥快速對接你的前端頁面》

相關文章
相關標籤/搜索