javascript task pool的设计与实现
来源:互联网 发布:自己网络用语怎么说 编辑:程序博客网 时间:2024/06/09 14:56
先简单描述一下这个 task pool的设计初衷,毕竟脱离实际谈设计都是耍流氓。
我需要这样一个Task Pool:
1. 能够指定任务运行的最大并发数量,超出运行池的任务进行排队;
2. 每一个任务都能够具备相当的灵活性;
3. 任务执行完成后能够根据任务的执行结果进行进一步的处理;
4. js中有大量的异步任务和回调,因此经常会有回调的顺序和依赖问题(例如等待多个异步请求执行完成再执行下一步);
5. 最终业务上使用简单,最好一行代码搞定;
结合一个ajax请求纳入任务池管理的具体业务,在进一步解释设计和实现之前介绍一下结构分层,这是我在设计过程中的基本原则:
1. 通用的task pool抽象: MergeableTaskPool (与业务无关,主要解决任务的调度、并发和排队的问题);
2. 通用的batch处理:BatchTaskPool (与业务无关,主要解决等待多个任务处理完成的问题,这多个任务之间可以并行);
3. 与业务相关的具体task manager:PooledAjaxManager(与业务相关,定义具体业务操作,离业务较近,因此封装需要足够轻量,摆脱重复干脏活累活);
4. 业务层接口:AjaxUtils(离用户/开发者最近的接口,因此易用性是最重要的);
在看task pool的代码实现前先看一下他的使用方式:
AjaxUtils.batchPost(postTasks,function(task, result) { console.log('post complete: ' + task.id); },function(batchId) { console.log('batch complete: ' + batchId) });
var AJAX_POOLED_MANAGER = new PooledAjaxManager(3); static batchPost(postTasks, callback, batchCallabck) { AJAX_POOLED_MANAGER.addBatch(BaseUtils.uniqId(), postTasks, callback, batchCallabck); }
具体业务层的实现也需要保持清爽,如果每个业务层pool的实现都要写大段的代码,那么Task Pool就没有任何设计可言了:
class PooledAjaxManager extends BatchTaskPool {constructor(limit) {super(limit);}process(taskId, task, resolve) {AjaxUtils.post(task.url, task.data, function(success, data) {resolve({success: success, data: data});});}}
简单描述下上面这段代码,这个是具体业务层manager的实现,与通用Task Pool解耦的思想也就包含在其中。这段代码只做了两件事:
1. 每一个manager可以独立指定任务的并发数量;
2. process 实现具体业务调用的逻辑,执行业务调用所需的数据都包含在之前传入pool的task对象中了。
这里比较重要的是resolve参数,因为javascript里处处都充满了异步处理和回调,因此怎么把异步做得更简单非常重要。因此我们提供了一个resolve方法,只要调用resolve方法就表明这个任务可以结束了,这和Promise的思想是相似的。同时resolve能够把任务的处理结果带回到业务层,也就是最上层封装单个任务执行完成通知回调的result参数。
这里没有贴出AjaxUtils.post方法的实现,因为实际上就是jQuery $.post 的简单调用而已,唯一的区别是把jquery ajax的 success 和 error 回调统一成了一个。
再下一层,当然就是通用 Task Pool的具体实现了,show me the code阶段(由于具体业务的需要):
class BatchTaskPool { constructor(limit) { this.callbacks = new MapArray(); // { taskId: [ callabck ... ] } this.taskBatches = new MapArray(); // { taskId: [ batchId ... ] } this.batchCallbacks = {}; // { batchId: batchCallback } this.batchTasks = {}; // { batchId: { taskId: } } this.taskPool = new MergeableTaskPool({ limit: limit || 5, process: this.process.bind(this), taskCallback: this.onTaskComplete.bind(this), complete: this.onTaskPoolIdle.bind(this) }); } // add single task add(task, callback) { let self = this; this.taskPool.push(task.id, task); this.callbacks.add(task.id, callback); } // add batch tasks addBatch(batchId, tasks, callback, batchCallback) { // map batchId to {taskId:true} let batchTaskMap = this.batchTasks[batchId] || {}; let manager = this; BaseUtils.each(tasks, function(task){ manager.callbacks.add(task.id, callback); manager.taskPool.push(task.id, task); manager.taskBatches.add(task.id, batchId); batchTaskMap[task.id] = true; }); this.batchTasks[batchId] = batchTaskMap; this.batchCallbacks[batchId] = batchCallback; } // process single task process(taskId, task, resolve) { console.warn('method "process" should be overrided!') setTimeout(resolve, 10); } // single task complete onTaskComplete(taskId, task, result) { let self = this; let callbacks = this.callbacks.remove(taskId); let taskBatches = this.taskBatches.get(taskId); BaseUtils.each(callbacks, function(callback){ ScCallback(callback, self, task, result); }) // remove taskId BaseUtils.each(taskBatches, function(batchId){ let batchIds = self.batchTasks[batchId]; if( batchIds ) { delete batchIds[taskId]; if( BaseUtils.isEmpty(batchIds)) { let batchCallback = self.batchCallbacks[batchId]; delete self.batchCallbacks[batchId]; self.onBatchComplete.call(this, batchId, batchCallback); } } }); } // batch complete onBatchComplete(batchId, batchCallback) { ScCallback(batchCallback, this, batchId); } onTaskPoolIdle() { }}
这里主要做的事情是为了实现批量回调的逻辑,这一点从constructor里初始化的数据结构里可以看出来。
class MergeableTaskPool { constructor(options) { this.options = options || {}; options = this.options; this.active = 0; this.limit = options.limit || 5; this.running = new MapArray(); this.queue = new LinkedSet(); this.cached = new MapArray(); } // add a task into pool, make it queued on running pool full filled push(key, task) { // key existed in running pool, need no wait for queue if( this.running.get(key)) { return this.running.add(key, task); } // execute task at once while there's quota in running queue if( this.active < this.limit) { return this.execute(key, task); } // otherwise, queue the task to be scheduled later return this.cache(key, task); } // execute task at one without wating execute(key, task) { this.running.add(key, task); this.active ++; // perform the task execute process, only execute the first node of the array ScCallback(this.options.process, this.options, key, task, this.resolve.bind(this, key)); } // add a task into queue of waiting scehduled cache(key, task) { this.queue.push(key); this.cached.add(key, task); } // schedule tasks to make sure the usage of running pool schedule() { let count = this.limit - this.active; let scheduleCount = Math.min(count, this.queue.length()); for( let i = 0; i < scheduleCount; i ++ ) { let key = this.queue.shift(); let tasks = this.cached.remove(key); if( tasks && tasks[0]) { this.execute(key, tasks[0]); } } } // mark running of key finished resolve(key, result) { if( this.running.get(key)) { let tasks = this.running.remove(key); this.active --; // call the complete callback while no more tasks need to be schedule let task = (tasks && tasks.length && tasks[0]) || {}; this.taskFinished.call(this, key, task, result); if( this.active == 0 && this.queue.isEmpty() ) { return this.taskPoolEmpty(); } } setTimeout( this.schedule.bind(this), 10); } taskFinished(key, task, result) { ScCallback(this.options.taskCallback, this, key, task, result); } // call the complete callback while no more tasks need to be schedule taskPoolEmpty() { ScCallback(this.options.complete, this); }}这里才是最底层的任务池实现,这里做了一点特殊处理,主要是为了避免重复处理单个任务。
这里也展现了resolve方法的具体实现,其中 setTimeout 10ms的做法还有待商榷。
在实现时基础数据结构的使用也很重要,虽然这里用的都是最简单的,但是有了这些基础结构以后就事半工倍了,平时工作中不时进行抽象和沉淀也是一个好习惯。
虽然这个设计和实现是围绕具体业务逻辑展开的,但是在做的时候能够更多地去思考他的更多的应用场景。例如,不妨试着回答一个问题:这里上层业务是为了让ajax请求单独new了一个专用ajax manager,如果业务里任务没有这么强的共性但是又需要batch处理的能力怎么办呢?那么回答应该也很简单,只需要把业务处理的function也打包进task对象就可以了,上层manager封装的process方法只需要调用task.func并确保resolve方法的调用就行了。
- javascript task pool的设计与实现
- JavaScript解释器的设计与实现
- Code Fragment-Message Pool的实现与处理
- Code Fragment-Message Pool的实现与处理
- JavaScript语言解释器的设计与实现
- 开坑!JavaScript AMD模块的设计与实现
- [设计模式]JavaScript钩子机制的应用场景与实现
- 关于Activity和task的设计思路与方法
- memory pool 的高效实现
- 需求的分拆与实现 – Story/Task
- Java小对象的解决之道——对象池(Object Pool)的设计与应用
- Java小对象的解决之道——对象池(Object Pool)的设计与应用
- Java小对象的解决之道——对象池(Object Pool)的设计与应用
- 资源池设计模式 (Resource Pool)和数据池的简单实现
- 池内春秋,Memory Pool 的设计哲学与无痛运用
- 设计Task Form的注意事项
- memory pool 的高效实现(代码)
- Objective-C Autorelease Pool 的实现原理
- ASProtect壳的分析
- 租用游艇问题
- Android 中获取EditText控件的焦点以及监听他的内容让焦点自动跳转到下一个EditText控件
- python-10-如何在一个for语句中迭代多个可迭代对象?
- 获取 非行间样式
- javascript task pool的设计与实现
- 早绑定的问题与动态绑定
- mysql初学
- DFS基础 POJ1321 棋盘问题
- path-sum
- 图像处理(卷积)
- improve your python code(5)
- ENVI标准格式转换为tif格式2
- c++ 中指针运算符->与成员运算符. 的区别