Ajax 板


LINE

再開始今天的主題前,因為小弟最近在看 ES6 所以以下會用到不少 ES6 才有的 syntax 例如 let http://es6.ruanyifeng.com/#docs/let class http://es6.ruanyifeng.com/#docs/class Promise http://es6.ruanyifeng.com/#docs/promise arrow function http://es6.ruanyifeng.com/#docs/function 如果有閱讀困難歡迎在推文留言,小弟會盡量回答 -- 我們先來回顧一下 Promise https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promisehttps://goo.gl/jIfcbN Promise 有幾個靜態方法: .all(), race() 其中 .all() 返回一個 Promise 物件 當參數中所有 Promise 物件都解決 (resolve) 後才跟著解決 因此所有參數 Promise 物件會同時併發,沒有限制數量 如果是做 fetch(), XMLHttpRequest() 等,可能會因為發起太多連線而被伺服器封鎖 但是原生的 Promise 物件並沒有提供限制並行數量的功能 所以我們有兩種選擇 1. 自幹 2. 找 lib 稍微翻了一下, async.js 其實就有提供這功能 但是因為我寫的是 user script 雖然可以用建置工具(如 Browserify)解決 import 問題 不過我還是希望盡量不靠額外的模組(code size 有差,雖然其實是不是問題的問題) 另外一個問題是我要做的是全域的並行數量限制 所以可能會在不同時間加入新的非同步請求 而不是一次執行所有非同步請求,再限制同時執行數量 所以其實我要做的其實是 Queue (隊列) 就像排隊買東西一樣,只有一個隊列 然後有 n 個櫃台可以同時服務使用者 所以我們可以訂下一個 API: class Queue { // 建立 Queue 物件,並行數量限制 limit 個 constructor(limit){} // 將 asyncFunction 放進 queue 中 // asyncFunction 將收到兩個參數 resolve, reject // 與 Promise() 的參數相同 // 回傳一個 Promise 物件 queue(asyncFunction){} // 內部方法:當數量充足時執行非同步請求 dequeue(){} } 接著先從 constructor() 著手 首先當然要把限制數量存起來 this.limit = limit; 接著建立一個陣列存放傳進來的非同步請求 this.q = []; 以及已經執行,等待解決的請求 this.slot = []; queue() 的部分回傳的是一個 Promise 物件 而 Promise 的接受的參數將會立即執行 所以這裡不能直接執行 asyncFunction 應該把 asyncFunction 連同 resolve(), reject() 放入 this.q 中 let _self = this; let job = new Promise( (resolve, reject) => { _self.q.push({ 'run': asyncFunction, 'resolver': resolve, 'rejector': reject }); }); 接著立即執行 dequeue(),若 slot 還有空間就執行請求 這裡把判斷限制數量放在之後的 dequeue 中 _self.dequeue(); 回傳剛剛建立的 Promise 物件 return job; 接著著手 dequeue(),第一件事當然是判斷 slot 有沒有空間 再把之前放進 q 的請求拿出來執行 let _self = this; if (_self.slot.length < _self.limit && _self.q.lenth >= 1) { let job = _self.q.shift(); _self.slot.push(job); job.run( (data) => { _self.removeJob(job); // will implement later setTimeout(_self.dequeue.bind(_self), 0); // will run after current function ends job.resolver(data); job = null; // always release memory }, (reason) => { _self.removeJob(job); setTimeout(_self.dequeue.bind(_self), 0); job.rejector(reason); job = null; }); } 這裡就變得比較複雜了,對吧? 還記得 Promise 物件的參數 executor 接受的參數 resolve 與 reject 嗎 我們先傳入自訂的 resolve() 與 reject() 進 asyncFunction 待 asyncFunction 結束請求而返回時,先將額外的事情處理完 才真正執行剛剛一起 push 進 q 的 resolve() 與 reject() 而下一行的 setTimeout 作用在於推遲下一個 dequeue 動作 先把目前處理中的請求結束掉,才能跑下一個請求 所以透過 setTimeout(dequeue, 0) 將其推遲至當前函數執行完才接著執行 接著再執行真正的 resolve 與 reject 讓剛剛 queue() 回傳的 Promise 有結果 而上述第一次出現的 removeJob() 則只有一個功能:移除 job(廢話) 程式碼只有短短幾行: Queue.prototype.removeJob = (job) => { let index = this.slot.indexOf(job); if (index >= 0) this.slot.splice(index, 1); }; 這樣我們就做完一個可以限制並行數量的非同步請求 Queue 了 \⊙▽⊙/ -- 那做完了要怎麼用呢? 首先要建立 Queue 物件: let q = new Queue(3); // max parallel limit: 3 接著放進 executor 並得到一個 Promise 物件 q.queue( (resolve, reject) => { XMLHttpRequest({ .... 'onload': resolve, 'onerror': reject }); }) .then( (data) => console.log(data.responseText)) .catch( (reason) => console.log(reason)); 或是透過 Promise.all 一次發出多個請求 Promise.all([asyncFunc1, asyncFunc2, ...].map(q.queue)) .then( (data) => console.log(data.responseText)) .catch( (reason) => console.log(reason)); -- 最後附上完整的 code https://gist.github.com/s25g5d4/c1bfa0569b9ef7e66d1aaa7a0b75e4dd/3e2347b0eec83771293de932a8bb7369f3a00b08 縮:https://goo.gl/Gc2euE 此程式已在 Firefox Developer Edtion 47.0a2 測試 如果要在不支援 ES6 的瀏覽器上使用,可以使用 Babel.js 轉換 另外這是我目前開發用的 code https://gist.github.com/s25g5d4/c1bfa0569b9ef7e66d1aaa7a0b75e4dd 主要差別在於 timeout 設計(很重要)與 job name (debug 用) 為什麼 timeout 很重要?因為全部的 slot 可能都被占用永遠不 resolve... 然後可能會再寫一篇解釋 timeout 怎麼寫 --
1F:→ eggimage:我之前也發生過很多次 yahoo本來就很爛 還外加奇摩12/10 18:52
2F:推 madeinchina:之前即時通死都不讓我登入 後來我就改用MSN了...12/10 18:53
3F:→ eggimage:發現MSN也不給你登....12/10 18:53
4F:推 madeinchina: 就改登PTT了12/10 18:55
5F:→ eggimage:最近ptt也一直斷....12/10 18:57
6F:→ freely10469: 只好掀桌出去裸奔了...12/10 19:57
--



※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 140.117.181.25
※ 文章網址: https://webptt.com/m.aspx?n=bbs/Ajax/M.1461516776.A.5CE.html ※ 編輯: s25g5d4 (140.117.181.25), 04/25/2016 01:00:50
7F:→ dlikeayu: 箭頭函式那邊應該不用事前 let _self = this 04/25 07:55
那是個人習慣,雖然我知道 context 沒有被改變 但就怕以後加 code 會出事
8F:→ dlikeayu: 用箭頭函式時已經等同宣告裏面的this是外面那層的了 04/25 07:55
9F:→ dlikeayu: 另外你專案再用webpack的loader來處理一下,供給大家你 04/25 07:57
10F:→ dlikeayu: 喜好的語法方式就比較沒有相容性問題了 04/25 07:57
我只有用 browserify 我是把 build task 寫在 npm 裡面: "scripts": { "build": "browserify index.js -o xxx.user.js -t [ babelify \ --presets [ es2015 stage-3 ] ] -p [ browserify-header --raw \ --file header.js ]" } 其實我是認為用 ES6 export 就可以了 雖然我沒用過 webpack 不過透過 babel 應該能正確處理 `export default Queue;` 不然就透過 `module.exportsx = Queue;`
11F:→ dlikeayu: 感謝你的分享 04/25 07:57
※ 編輯: s25g5d4 (140.117.247.129), 04/25/2016 10:25:36 ※ 編輯: s25g5d4 (140.117.247.129), 04/25/2016 10:29:57
12F:→ No: 你這寫法 [].map(q.queue) 會噴掉喔 04/26 01:03







like.gif 您可能會有興趣的文章
icon.png[問題/行為] 貓晚上進房間會不會有憋尿問題
icon.pngRe: [閒聊] 選了錯誤的女孩成為魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一張
icon.png[心得] EMS高領長版毛衣.墨小樓MC1002
icon.png[分享] 丹龍隔熱紙GE55+33+22
icon.png[問題] 清洗洗衣機
icon.png[尋物] 窗台下的空間
icon.png[閒聊] 双極の女神1 木魔爵
icon.png[售車] 新竹 1997 march 1297cc 白色 四門
icon.png[討論] 能從照片感受到攝影者心情嗎
icon.png[狂賀] 賀賀賀賀 賀!島村卯月!總選舉NO.1
icon.png[難過] 羨慕白皮膚的女生
icon.png閱讀文章
icon.png[黑特]
icon.png[問題] SBK S1安裝於安全帽位置
icon.png[分享] 舊woo100絕版開箱!!
icon.pngRe: [無言] 關於小包衛生紙
icon.png[開箱] E5-2683V3 RX480Strix 快睿C1 簡單測試
icon.png[心得] 蒼の海賊龍 地獄 執行者16PT
icon.png[售車] 1999年Virage iO 1.8EXi
icon.png[心得] 挑戰33 LV10 獅子座pt solo
icon.png[閒聊] 手把手教你不被桶之新手主購教學
icon.png[分享] Civic Type R 量產版官方照無預警流出
icon.png[售車] Golf 4 2.0 銀色 自排
icon.png[出售] Graco提籃汽座(有底座)2000元誠可議
icon.png[問題] 請問補牙材質掉了還能再補嗎?(台中半年內
icon.png[問題] 44th 單曲 生寫竟然都給重複的啊啊!
icon.png[心得] 華南紅卡/icash 核卡
icon.png[問題] 拔牙矯正這樣正常嗎
icon.png[贈送] 老莫高業 初業 102年版
icon.png[情報] 三大行動支付 本季掀戰火
icon.png[寶寶] 博客來Amos水蠟筆5/1特價五折
icon.pngRe: [心得] 新鮮人一些面試分享
icon.png[心得] 蒼の海賊龍 地獄 麒麟25PT
icon.pngRe: [閒聊] (君の名は。雷慎入) 君名二創漫畫翻譯
icon.pngRe: [閒聊] OGN中場影片:失蹤人口局 (英文字幕)
icon.png[問題] 台灣大哥大4G訊號差
icon.png[出售] [全國]全新千尋侘草LED燈, 水草

請輸入看板名稱,例如:WOW站內搜尋

TOP