前幾天在做一個(gè)抽獎(jiǎng)轉(zhuǎn)盤(pán)的時(shí)候公条,突然集中用到promise api所以記錄并總結(jié)一二贝攒。
需求是做一個(gè)如下的轉(zhuǎn)盤(pán)锌杀,轉(zhuǎn)盤(pán)上的圖片和文字是從后端字段返回的。
文字的繪制倒是問(wèn)題不大逝钥,難點(diǎn)在于圖片屑那,眾所周知canvas的context.drawImage(img element)所繪制的圖片需要是已經(jīng)保證onload的img對(duì)象,當(dāng)時(shí)我的第一想法是拿到后端圖片數(shù)組之后依次onload它艘款,并塞進(jìn)一個(gè)新的數(shù)組持际,數(shù)組長(zhǎng)度 = 后端數(shù)組長(zhǎng)度,再調(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)盤(pán)方法
}
}
}
至少一開(kāi)始我也覺(jué)得沒(méi)問(wè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í)是不可靠的展鸡。
后來(lái)大佬看到我在糾結(jié)于此,便提出你可以參考一下我的做法隨手便甩給我一個(gè)倉(cāng)庫(kù)地址埃难,我看到第一眼promise all這個(gè)api我就知道了大概做法(也就是說(shuō)其實(shí)我并沒(méi)有看大佬后面的具體實(shí)現(xiàn)2333莹弊,不過(guò)我猜想應(yīng)該差不多)。這個(gè)api第一次用還是當(dāng)時(shí)為了應(yīng)付面試所以臨時(shí)學(xué)習(xí)的涡尘,事實(shí)上工作中前端業(yè)務(wù)所用到的場(chǎng)景并不多箱硕,一般在后端nodejs上可能對(duì)于隊(duì)列之間有嚴(yán)格前后順序依賴(lài)關(guān)系的業(yè)務(wù)會(huì)比較有幫助,一般前端需求promise單api即可完成所以使用不多悟衩,但恰好canvas這個(gè)drawimage需要的就是加載完的圖片,而且需要順序正確栓拜。
下面是具體代碼座泳,在preloadImage我們把img的onload這一過(guò)程作為promise對(duì)象存進(jìn)隊(duì)列,也就是說(shuō)其實(shí)你打印this.renderList 會(huì)得到[promise, promise,promise,promise幕与,....]這樣一個(gè)數(shù)組挑势, 這個(gè)promise對(duì)象返回值就是我們已經(jīng)onload成功的img對(duì)象,再通過(guò)promise all啦鸣,它等待所有返回完成再執(zhí)行后面的繪制過(guò)程潮饱,雖然這個(gè)過(guò)程可能會(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)盤(pá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
}))
},