?最近在一個項目中蝇闭,遇到這么一個需求:一個頁面中罕偎,大概有四五個元素需要按一定次序依次進場,setTimeout來實現(xiàn)吧陌知,仔細一想他托,那樣的代碼實在是寫不下去,大概是這樣的:
setTimeout(()=>{
this.setState({view1Visible: true})
setTimeout(()=>{
this.setState({view2Visible: true})
setTimeout(()=>{
this.setState({view3Visible: true})
setTimeout(()=>{
// 沒完沒了的setTimout...
},500)
},500)
},500)
},100)
?明顯的回調(diào)地獄仆葡,對癥下藥赏参,用Promise來簡單封裝一下:
const timer=(task,ms)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
task && task()
resolve()
},ms)
})
}
?然后之前的代碼大致可以寫成這樣:
timer(()=>this.setState({view1Visible: true}),100)
.then(()=>timer(()=>this.setState({view2Visible: true}),500))
.then(()=>timer(()=>this.setState({view3Visible: true}),500))
.then(()=>timer(()=>this.setState({view4Visible: true}),500))
?到這里基本已經(jīng)滿足我的需求了,如果對不喜歡用then沿盅,或者只是對它有意見把篓,也可以用async/await來改寫一下:
async layout(){
await timer(()=>this.setState({view1Visible: true}),100)
await timer(()=>this.setState({view2Visible: true}),500)
await timer(()=>this.setState({view3Visible: true}),500)
await timer(()=>this.setState({view4Visible: true}),500)
}
?寫到這里,已經(jīng)足夠了腰涧,不過我個人對timer的兩個參數(shù)不喜歡纸俭,而且我更喜歡寫鏈式風格的代碼,理想的代碼是這樣的:
new Schedule()
.delay(100).task(()=>this.setState({view1Visible:true}))
.task(()=>this.setState({view1Visible:true}))
.task(()=>this.setState({view2Visible:true}))
.task(()=>this.setState({view3Visible:true}))
?首先task和delay分別用兩個方法傳參南窗,語義化嘛,一眼就能看出這個參數(shù)指的是什么郎楼;然后delay要能夠復(fù)用万伤,很多情下我們?nèi)蝿?wù)之間的間隔是相等的,就不用每次都傳了呜袁。
?實現(xiàn)方法嘛敌买,在Schedule類中,要有個promise來處理這些任務(wù)阶界,然后需要一個變量來保存delay虹钮,來達到復(fù)用的目的,然后就是delay和task兩個方法膘融,都返回this來實現(xiàn)鏈式調(diào)用芙粱。最后把上面那個timer方法拿過來,解決回調(diào)地獄氧映。先看看最后的代碼吧:
export default class Schedule{
constructor(){
this._delay=0
this.p = null
}
timer(task,ms){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
task && task()
resolve()
},ms)
})
}
task(task){
const {_delay:delay,timer,p}=this
this.p = p ?p.then(()=>timer(task,delay)) :timer(task,delay)
return this
}
delay(_delay){
this._delay = _delay
return this
}
}
?也沒啥特別的春畔,要注意的一點是,第一次調(diào)用task的時候岛都,p為空律姨,直接給他賦值即可【室撸或者你一可以給p一個初始的promise择份,之后就不用考慮是否為空了,直接p.then()
就可以了烫堤,需要先用一個臨時變量把delay緩存起來荣赶,否則最后再執(zhí)行到當前task的時候凤价,delay很有可能取到的是后面賦的值。
?對于一般的需求讯壶,現(xiàn)在這個Schedule應(yīng)該完全能夠搞定料仗,可能你想這樣做:先把任務(wù)隊列定義好,到了特定的時機再去觸發(fā)它執(zhí)行伏蚊,那我們要怎么做呢立轧?
?其實也不難,每次調(diào)用task的時候躏吊,不放到promise里面氛改,而是把task和當前delay先保存到一個數(shù)組里面,最后再寫一個方法比伏,在調(diào)用的時候遍歷這個數(shù)組胜卤,把他們放到promise里面去,直接上代碼好了:
export default class Schedule{
constructor(){
this._delay=0
this.tasks=[]
}
timer(task,ms){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
task && task()
resolve()
},ms)
})
}
task(task){
this.tasks.push({task,delay:this._delay})
return this
}
delay(_delay){
this._delay = _delay
return this
}
exec(){
this.tasks.length>0 && this.tasks.reduce(
(p,t)=>p.then(()=>this.timer(t.task,t.delay)),
Promise.resolve()
)
}
}
?一個小小的技巧就是用數(shù)組的reduce方法來把這些task依次放到promise中赁项,在reduce的第二個參數(shù)傳入一個空的Promise葛躏,就避免了判斷是否有初始Promise的問題。用的時候需要手動去調(diào)用exec方法悠菜,整個隊列才回開始執(zhí)行:
new Schedule()
.delay(100).task(()=>this.setState({view1Visible:true}))
.task(()=>this.setState({view1Visible:true}))
.task(()=>this.setState({view2Visible:true}))
.task(()=>this.setState({view3Visible:true}))
.exec() // 可以在任何你需要的時候調(diào)用
?需要介紹的就這些了舰攒,最后其實有不少可以改進的地方,比如上面說的兩種情況悔醋,完全可以寫在一起摩窃,構(gòu)造方法中傳個參數(shù)來決定是否是需要延遲執(zhí)行的隊列。又或者引入cron表達式芬骄,來決定在特定的時間點執(zhí)行任務(wù)……當然這些不在本文討論的范疇猾愿,感興趣的朋友可以去試試。