溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JS下大批量異步任務按順序執行的示例分析

發布時間:2021-08-05 10:58:04 來源:億速云 閱讀:205 作者:小新 欄目:web開發

這篇文章主要介紹了JS下大批量異步任務按順序執行的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

前言

最近需要做一個瀏覽器的, 支持大體積文件上傳且要支持斷點續傳的上傳組件, 本來以為很容易的事情, 結果碰到了一個有意思的問題:

循環執行連續的異步任務, 且后一個任務需要等待前一個任務的執行狀態

這么說可能有點空泛, 以我做的組件舉例:

這個組件本意是為了上傳大體積視頻, 和支持斷點續傳, 因為動輒幾個G的視頻不可能直接把文件讀進內存, 只能分片發送(考慮到實際網絡狀態, 每次發送大小定在了4MB), 而且這么做也符合斷點續傳的思路.

組件工作流程如下:

  1. 選定上傳文件后, 從H5原生upload組件里取得文件的blob對象  (同步)

  2. 通過blob對象的slice方法把文件切片  (同步)

  3. 新建一個Filereader對象, 通過Filereader的readAsArrayBuffer方法讀取步驟2中生成的slice  (異步)

  4. 如果步驟3的buffer讀取成功(通過監控Filereader的onload事件), 則ajax發送步驟3中的buffer  (異步)

  5. 如果ajax發送成功, 且服務器儲存完成, 會向客戶端發回一個成功狀態碼, 如果ajax的response中存在這個狀態碼, 則進行下一次切片發送  (異步)

從組件工作流程可以發現, 3,4,5中的連續異步任務, 必須要按順序進行, 且每一步任務間存在相互依賴, 最后還要對這些步驟進行多次循環.

如果只是處理單次的連續異步任務, 通過promise鏈式調用即可, 但是要循環執行這樣的連續異步任務讓我想了很久.

后來google了很久也沒發現解決方案, 無奈下閉門造車了2天, 想出了3套方案, 權當拋磚引玉, 希望各位給出更好建議

3套方案的核心思想相同, 類似觀察者模式, 來控制循環的進行, 區別在于循環的實現不同, 實際上這3套方案也是我自我否定的過程, 不斷思考更好的方法, 整個組件代碼略長, 在此只挑出問題相關部分, 且省略錯誤處理部分

方案1

依然以上傳組件舉例

//循環狀態標記,0為初始狀態,1為正常,2為出錯
let status = 0;

/* 新建Filereader,讀取文件切片,返回一個promise
* 把讀取成功的arraybuffer通過reslove傳出
*/
const createReader = ()=> {
 return new Promise ((reslove, reject)=> {
  let reader = new Filereader();
  ...
  reader.onload = ()=> {
   reslove(reader.result)
  }
  reader.onerror = ()=> reject()
 })
}

// ajax發送createReader方法讀取到的Buff
const createXhr = ()=> {
 const xhr= new XMLHttpRequest();
 return new Promise ((reslove, reject)=> {
  ...
  xhr.onreadystatechange= ()=> {
   ...
   //如果readyState == 4,status == 200且服務器的狀態碼存在,更改全局標記為1
   status = 1;
   reslove()
  }
 })
}

//每一輪循環開始前都檢查一次全局狀態標記
const checkStatus = ()=> {
 ...
 if (status == 1) {
  loop()
 }
}

//循環過程的鏈式調用
const loop = ()=> {
 createReader().then(()=> createXhr()).then(()=> checkStatus());
}

方案1是基于初見問題的'想當然'解決方法, 碰到異步任務就promise, 這樣的循環長鏈調用, 寫法不優雅, 且錯誤調試異常麻煩, 更爆炸的是因為閉包問題, 在循環執行中這些內存難以回收, 內存消耗急劇增加, 只能等待循環執行完成

方案2

徹底引入觀察者模式, 構造一個簡單的EventEmitter, 通過event.on, event.emit的形式完成循環

//模仿node.js的EventEmitter
class EventEmitter {
 constructor() {
  this.handler = {};
 }
 on(eventName, callback) {
  if (!this.handles){
   this.handles = {};
  }
  if (!this.handles[eventName]) {
   this.handles[eventName] = [];
  }
  this.handles[eventName].push(callback);
 }
 emit(eventName,...arg) {
  if (this.handles[eventName]) {
   for (var i=0;i<this.handles[eventName].length;i++) {
    this.handles[eventName][i](...arg);
   }
  }
 }
 }

let ev= new EventEmitter();
...
//監聽createReader事件,如果讀取buffer成功就觸發toajax事件來上傳切片
ev.on('createReader', ()=> {
 let reader = new Filereader();
 ...
 reader.onload = ()=> {
  ev.emit('toajax')
 }
})

//監聽toajax事件,如果上傳成功,就觸發createReader事件開始讀取下一切片
ev.on('toajax', ()=> {
 let xhr= new XMLHttpRequest();
 ...
 xhr.onreadystatechange = ()=> {
 //如果readyState == 4,status == 200且服務器的狀態碼存在
  ev.emit('createReader')
 }
})

方案2徹底貫徹'事件', 代碼語義更自然, 錯誤調試也比方案1更為簡單, 但內存泄漏問題依然存在

方案3

方案3, 回歸方案1的狀態管理方式, 但是通過setInterval方法來實現循環.

//全局狀態標記
let status = 0;

//讀取切片
const createReader = ()=> {
 let reader = new Filereader();
 ...
 reader.onload = ()=>status = 1
}

//上傳切片
const createXhr = ()=> {
 let xhr= new XMLHttpRequest();
 ...
 xhr.onreadystatechange = ()=> {
  ...
  //如果readyState == 4,status == 200且服務器的狀態碼存在
  status = 2
 }
}

/* 設置一個間隔時間極短的計時器,根據status決定下一步的任務,
* 上傳完成后定時器自動清除自己
* 另外有判斷文件是否上傳完成的方法,這里就不寫了
*/
let timer = setInterval(()=> {
 if (status == 2) {
  createReader();
 } else if (status == 1) {
  createXhr();
 } else if (status == 3) {
  clearInterval(timer);
 }
},10)

不可否認, 方案3看上去很low, 如果追求極致的執行效率, 方案3無疑是最蠢的辦法, 但是方案三相當于把異步任務轉化為了同步任務, 語義簡潔, 且沒有上面2種方法的內存泄漏問題.

方案3本質上是把while (true)改寫成了setInterval, 因為while true會阻塞線程, 各種異步事件的回調也會被一同阻塞, 所以選擇了setInterval

感謝你能夠認真閱讀完這篇文章,希望小編分享的“JS下大批量異步任務按順序執行的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

js
AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女