前幾天在做一個(gè)抽獎(jiǎng)轉(zhuǎn)盤的時(shí)候,突然集中用到promise api所以記錄并總結(jié)一二。
需求是做一個(gè)如下的轉(zhuǎn)盤,轉(zhuǎn)盤上的圖片和文字是從后端字段返回的。

文字的繪制倒是問題不大,難點(diǎn)在于圖片,眾所周知canvas的context.drawImage(img element)所繪制的圖片需要是已經(jīng)保證onload的img對(duì)象,當(dāng)時(shí)我的第一想法是拿到后端圖片數(shù)組之后依次onload它,并塞進(jìn)一個(gè)新的數(shù)組,數(shù)組長度 = 后端數(shù)組長度,再調(diào)用繪制api不就行了,偽代碼如下
const newArr = []
for (let i = 0; i <arr.length; i++) {
arr[i].onload = () => {
newArr.push(arr[i])
if (newArr.length === 后端圖片數(shù)組.length) {
調(diào)用方法繪制轉(zhuǎn)盤方法
}
}
}
至少一開始我也覺得沒問題的,但是發(fā)現(xiàn)只要你清空緩存(第一次進(jìn)入)圖片順序就不對(duì)了,抽獎(jiǎng)的順序不對(duì)那可不是小事情,于是乎我console了一下如上代碼的輸出數(shù)組的圖片順序,我發(fā)現(xiàn)他并不會(huì)一個(gè)一個(gè)按照順序的塞入數(shù)組,想到for和onload的執(zhí)行原理,它onload完了才會(huì)給你這個(gè)回調(diào),那確實(shí)是不可靠的。
后來大佬看到我在糾結(jié)于此,便提出你可以參考一下我的做法隨手便甩給我一個(gè)倉庫地址,我看到第一眼promise all這個(gè)api我就知道了大概做法(也就是說其實(shí)我并沒有看大佬后面的具體實(shí)現(xiàn)2333,不過我猜想應(yīng)該差不多)。這個(gè)api第一次用還是當(dāng)時(shí)為了應(yīng)付面試所以臨時(shí)學(xué)習(xí)的,事實(shí)上工作中前端業(yè)務(wù)所用到的場景并不多,一般在后端nodejs上可能對(duì)于隊(duì)列之間有嚴(yán)格前后順序依賴關(guān)系的業(yè)務(wù)會(huì)比較有幫助,一般前端需求promise單api即可完成所以使用不多,但恰好canvas這個(gè)drawimage需要的就是加載完的圖片,而且需要順序正確。
下面是具體代碼,在preloadImage我們把img的onload這一過程作為promise對(duì)象存進(jìn)隊(duì)列,也就是說其實(shí)你打印this.renderList 會(huì)得到[promise, promise,promise,promise,....]這樣一個(gè)數(shù)組, 這個(gè)promise對(duì)象返回值就是我們已經(jīng)onload成功的img對(duì)象,再通過promise all,它等待所有返回完成再執(zhí)行后面的繪制過程,雖然這個(gè)過程可能會(huì)有一點(diǎn)點(diǎn)滯后但是對(duì)于這種強(qiáng)順序關(guān)系并且需要已經(jīng)加載完成的事件,這個(gè)api就發(fā)揮的淋漓精致了。
async renderTheWheelImage(params) { // params : ['圖片src', '圖片src', ....]
for (let i = 0; i < params.length; i++) {
await this.preloadImage(params[i])
}
Promise.all(this.renderList).then((res) => {
// 繪制轉(zhuǎn)盤的方法(res)
})
},
preloadImage(item) {
this.renderList.push(new Promise((resolve, reject) => {
const newImage = new Image()
newImage.onload = () => {
resolve(newImage)
}
newImage.onerror = reject
newImage.src = item
}))
},