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/cn.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