js異步任務(wù)隊(duì)列

?最近在一個(gè)項(xiàng)目中,遇到這么一個(gè)需求:一個(gè)頁(yè)面中,大概有四五個(gè)元素需要按一定次序依次進(jìn)場(chǎng),setTimeout來(lái)實(shí)現(xiàn)吧,仔細(xì)一想,那樣的代碼實(shí)在是寫(xiě)不下去,大概是這樣的:

setTimeout(()=>{
    this.setState({view1Visible: true})
    setTimeout(()=>{
        this.setState({view2Visible: true})
        setTimeout(()=>{
            this.setState({view3Visible: true})
            setTimeout(()=>{
                // 沒(méi)完沒(méi)了的setTimout...
            },500)
        },500)
    },500)
},100)

?明顯的回調(diào)地獄,對(duì)癥下藥,用Promise來(lái)簡(jiǎn)單封裝一下:

const timer=(task,ms)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            task && task()
            resolve()
        },ms)
    })
}

?然后之前的代碼大致可以寫(xiě)成這樣:

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)滿足我的需求了,如果對(duì)不喜歡用then,或者只是對(duì)它有意見(jiàn),也可以用async/await來(lái)改寫(xiě)一下:

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)
}

?寫(xiě)到這里,已經(jīng)足夠了,不過(guò)我個(gè)人對(duì)timer的兩個(gè)參數(shù)不喜歡,而且我更喜歡寫(xiě)鏈?zhǔn)斤L(fēng)格的代碼,理想的代碼是這樣的:

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分別用兩個(gè)方法傳參,語(yǔ)義化嘛,一眼就能看出這個(gè)參數(shù)指的是什么;然后delay要能夠復(fù)用,很多情下我們?nèi)蝿?wù)之間的間隔是相等的,就不用每次都傳了。

?實(shí)現(xiàn)方法嘛,在Schedule類中,要有個(gè)promise來(lái)處理這些任務(wù),然后需要一個(gè)變量來(lái)保存delay,來(lái)達(dá)到復(fù)用的目的,然后就是delay和task兩個(gè)方法,都返回this來(lái)實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。最后把上面那個(gè)timer方法拿過(guò)來(lái),解決回調(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
  }
}

?也沒(méi)啥特別的,要注意的一點(diǎn)是,第一次調(diào)用task的時(shí)候,p為空,直接給他賦值即可。或者你一可以給p一個(gè)初始的promise,之后就不用考慮是否為空了,直接p.then()就可以了,需要先用一個(gè)臨時(shí)變量把delay緩存起來(lái),否則最后再執(zhí)行到當(dāng)前task的時(shí)候,delay很有可能取到的是后面賦的值。

?對(duì)于一般的需求,現(xiàn)在這個(gè)Schedule應(yīng)該完全能夠搞定,可能你想這樣做:先把任務(wù)隊(duì)列定義好,到了特定的時(shí)機(jī)再去觸發(fā)它執(zhí)行,那我們要怎么做呢?

?其實(shí)也不難,每次調(diào)用task的時(shí)候,不放到promise里面,而是把task和當(dāng)前delay先保存到一個(gè)數(shù)組里面,最后再寫(xiě)一個(gè)方法,在調(diào)用的時(shí)候遍歷這個(gè)數(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()
    )
  }
}

?一個(gè)小小的技巧就是用數(shù)組的reduce方法來(lái)把這些task依次放到promise中,在reduce的第二個(gè)參數(shù)傳入一個(gè)空的Promise,就避免了判斷是否有初始Promise的問(wèn)題。用的時(shí)候需要手動(dòng)去調(diào)用exec方法,整個(gè)隊(duì)列才回開(kāi)始執(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() // 可以在任何你需要的時(shí)候調(diào)用

?需要介紹的就這些了,最后其實(shí)有不少可以改進(jìn)的地方,比如上面說(shuō)的兩種情況,完全可以寫(xiě)在一起,構(gòu)造方法中傳個(gè)參數(shù)來(lái)決定是否是需要延遲執(zhí)行的隊(duì)列。又或者引入cron表達(dá)式,來(lái)決定在特定的時(shí)間點(diǎn)執(zhí)行任務(wù)……當(dāng)然這些不在本文討論的范疇,感興趣的朋友可以去試試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 那些年我在乎過(guò)很多,喜歡過(guò)很多,嘗試過(guò)看得到的新鮮。只是為了一個(gè)很簡(jiǎn)單的理由,后來(lái)那些在乎不知去了何方。后來(lái)我就開(kāi)...
    夜落y閱讀 548評(píng)論 0 5
  • 1 我出生在蘇北地區(qū),挨著山東。從小耳聞目睹家務(wù)都是婦女來(lái)干,洗衣服、做飯,不夸張地說(shuō)男人們油瓶倒了都不帶扶的。 ...
    流年芳華閱讀 1,562評(píng)論 2 5
  • 2017.12.08 編號(hào)28 日精進(jìn)554天 體驗(yàn)入: 愛(ài)的三大表現(xiàn):1希望他好! 2愿意犧牲自己為他好! 3愿...
    宇宙之愛(ài)黃昊貴閱讀 273評(píng)論 0 0
  • 當(dāng)走出學(xué)院推免面試的門的時(shí)候,我知道我可以了。 我以為我會(huì)擠下幾滴眼淚,就像一個(gè)月之前在重慶的酒店里,離開(kāi)重慶之前...
    real_x閱讀 403評(píng)論 0 0