| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** | |
| 8 * Namespace for async utility functions. | |
| 9 */ | |
| 10 var AsyncUtil = {}; | |
| 11 | |
| 12 /** | |
| 13 * Asynchronous version of Array.forEach. | |
| 14 * This executes a provided function callback once per array element, then | |
| 15 * run completionCallback to notify the completion. | |
| 16 * The callback can be an asynchronous function, but the execution is | |
| 17 * sequentially done. | |
| 18 * | |
| 19 * @param {Array.<T>} array The array to be iterated. | |
| 20 * @param {function(function(), T, number, Array.<T>} callback The iteration | |
| 21 * callback. The first argument is a callback to notify the completion of | |
| 22 * the iteration. | |
| 23 * @param {function()} completionCallback Called when all iterations are | |
| 24 * completed. | |
| 25 * @param {Object=} opt_thisObject Bound to callback if given. | |
| 26 * @template T | |
| 27 */ | |
| 28 AsyncUtil.forEach = function( | |
| 29 array, callback, completionCallback, opt_thisObject) { | |
| 30 if (opt_thisObject) | |
| 31 callback = callback.bind(opt_thisObject); | |
| 32 | |
| 33 var queue = new AsyncUtil.Queue(); | |
| 34 for (var i = 0; i < array.length; i++) { | |
| 35 queue.run(function(element, index, iterationCompletionCallback) { | |
| 36 callback(iterationCompletionCallback, element, index, array); | |
| 37 }.bind(null, array[i], i)); | |
| 38 } | |
| 39 queue.run(function(iterationCompletionCallback) { | |
| 40 completionCallback(); // Don't pass iteration completion callback. | |
| 41 }); | |
| 42 }; | |
| 43 | |
| 44 /** | |
| 45 * Creates a class for executing several asynchronous closures in a fifo queue. | |
| 46 * Added tasks will be executed sequentially in order they were added. | |
| 47 * | |
| 48 * @constructor | |
| 49 */ | |
| 50 AsyncUtil.Queue = function() { | |
| 51 this.running_ = false; | |
| 52 this.closures_ = []; | |
| 53 }; | |
| 54 | |
| 55 /** | |
| 56 * @return {boolean} True when a task is running, otherwise false. | |
| 57 */ | |
| 58 AsyncUtil.Queue.prototype.isRunning = function() { | |
| 59 return this.running_; | |
| 60 }; | |
| 61 | |
| 62 /** | |
| 63 * Enqueues a closure to be executed. | |
| 64 * @param {function(function())} closure Closure with a completion callback to | |
| 65 * be executed. | |
| 66 */ | |
| 67 AsyncUtil.Queue.prototype.run = function(closure) { | |
| 68 this.closures_.push(closure); | |
| 69 if (!this.running_) | |
| 70 this.continue_(); | |
| 71 }; | |
| 72 | |
| 73 /** | |
| 74 * Serves the next closure from the queue. | |
| 75 * @private | |
| 76 */ | |
| 77 AsyncUtil.Queue.prototype.continue_ = function() { | |
| 78 if (!this.closures_.length) { | |
| 79 this.running_ = false; | |
| 80 return; | |
| 81 } | |
| 82 | |
| 83 // Run the next closure. | |
| 84 this.running_ = true; | |
| 85 var closure = this.closures_.shift(); | |
| 86 closure(this.continue_.bind(this)); | |
| 87 }; | |
| 88 | |
| 89 /** | |
| 90 * Cancels all pending tasks. Note that this does NOT cancel the task running | |
| 91 * currently. | |
| 92 */ | |
| 93 AsyncUtil.Queue.prototype.cancel = function() { | |
| 94 this.closures_ = []; | |
| 95 }; | |
| 96 | |
| 97 /** | |
| 98 * Creates a class for executing several asynchronous closures in a group in | |
| 99 * a dependency order. | |
| 100 * | |
| 101 * @constructor | |
| 102 */ | |
| 103 AsyncUtil.Group = function() { | |
| 104 this.addedTasks_ = {}; | |
| 105 this.pendingTasks_ = {}; | |
| 106 this.finishedTasks_ = {}; | |
| 107 this.completionCallbacks_ = []; | |
| 108 }; | |
| 109 | |
| 110 /** | |
| 111 * Enqueues a closure to be executed after dependencies are completed. | |
| 112 * | |
| 113 * @param {function(function())} closure Closure with a completion callback to | |
| 114 * be executed. | |
| 115 * @param {Array.<string>=} opt_dependencies Array of dependencies. If no | |
| 116 * dependencies, then the the closure will be executed immediately. | |
| 117 * @param {string=} opt_name Task identifier. Specify to use in dependencies. | |
| 118 */ | |
| 119 AsyncUtil.Group.prototype.add = function(closure, opt_dependencies, opt_name) { | |
| 120 var length = Object.keys(this.addedTasks_).length; | |
| 121 var name = opt_name || ('(unnamed#' + (length + 1) + ')'); | |
| 122 | |
| 123 var task = { | |
| 124 closure: closure, | |
| 125 dependencies: opt_dependencies || [], | |
| 126 name: name | |
| 127 }; | |
| 128 | |
| 129 this.addedTasks_[name] = task; | |
| 130 this.pendingTasks_[name] = task; | |
| 131 }; | |
| 132 | |
| 133 /** | |
| 134 * Runs the enqueued closured in order of dependencies. | |
| 135 * | |
| 136 * @param {function()=} opt_onCompletion Completion callback. | |
| 137 */ | |
| 138 AsyncUtil.Group.prototype.run = function(opt_onCompletion) { | |
| 139 if (opt_onCompletion) | |
| 140 this.completionCallbacks_.push(opt_onCompletion); | |
| 141 this.continue_(); | |
| 142 }; | |
| 143 | |
| 144 /** | |
| 145 * Runs enqueued pending tasks whose dependencies are completed. | |
| 146 * @private | |
| 147 */ | |
| 148 AsyncUtil.Group.prototype.continue_ = function() { | |
| 149 // If all of the added tasks have finished, then call completion callbacks. | |
| 150 if (Object.keys(this.addedTasks_).length == | |
| 151 Object.keys(this.finishedTasks_).length) { | |
| 152 for (var index = 0; index < this.completionCallbacks_.length; index++) { | |
| 153 var callback = this.completionCallbacks_[index]; | |
| 154 callback(); | |
| 155 } | |
| 156 this.completionCallbacks_ = []; | |
| 157 return; | |
| 158 } | |
| 159 | |
| 160 for (var name in this.pendingTasks_) { | |
| 161 var task = this.pendingTasks_[name]; | |
| 162 var dependencyMissing = false; | |
| 163 for (var index = 0; index < task.dependencies.length; index++) { | |
| 164 var dependency = task.dependencies[index]; | |
| 165 // Check if the dependency has finished. | |
| 166 if (!this.finishedTasks_[dependency]) | |
| 167 dependencyMissing = true; | |
| 168 } | |
| 169 // All dependences finished, therefore start the task. | |
| 170 if (!dependencyMissing) { | |
| 171 delete this.pendingTasks_[task.name]; | |
| 172 task.closure(this.finish_.bind(this, task)); | |
| 173 } | |
| 174 } | |
| 175 }; | |
| 176 | |
| 177 /** | |
| 178 * Finishes the passed task and continues executing enqueued closures. | |
| 179 * | |
| 180 * @param {Object} task Task object. | |
| 181 * @private | |
| 182 */ | |
| 183 AsyncUtil.Group.prototype.finish_ = function(task) { | |
| 184 this.finishedTasks_[task.name] = task; | |
| 185 this.continue_(); | |
| 186 }; | |
| 187 | |
| 188 /** | |
| 189 * Aggregates consecutive calls and executes the closure only once instead of | |
| 190 * several times. The first call is always called immediately, and the next | |
| 191 * consecutive ones are aggregated and the closure is called only once once | |
| 192 * |delay| amount of time passes after the last call to run(). | |
| 193 * | |
| 194 * @param {function()} closure Closure to be aggregated. | |
| 195 * @param {number=} opt_delay Minimum aggregation time in milliseconds. Default | |
| 196 * is 50 milliseconds. | |
| 197 * @constructor | |
| 198 */ | |
| 199 AsyncUtil.Aggregation = function(closure, opt_delay) { | |
| 200 /** | |
| 201 * @type {number} | |
| 202 * @private | |
| 203 */ | |
| 204 this.delay_ = opt_delay || 50; | |
| 205 | |
| 206 /** | |
| 207 * @type {function()} | |
| 208 * @private | |
| 209 */ | |
| 210 this.closure_ = closure; | |
| 211 | |
| 212 /** | |
| 213 * @type {number?} | |
| 214 * @private | |
| 215 */ | |
| 216 this.scheduledRunsTimer_ = null; | |
| 217 | |
| 218 /** | |
| 219 * @type {number} | |
| 220 * @private | |
| 221 */ | |
| 222 this.lastRunTime_ = 0; | |
| 223 }; | |
| 224 | |
| 225 /** | |
| 226 * Runs a closure. Skips consecutive calls. The first call is called | |
| 227 * immediately. | |
| 228 */ | |
| 229 AsyncUtil.Aggregation.prototype.run = function() { | |
| 230 // If recently called, then schedule the consecutive call with a delay. | |
| 231 if (Date.now() - this.lastRunTime_ < this.delay_) { | |
| 232 this.cancelScheduledRuns_(); | |
| 233 this.scheduledRunsTimer_ = setTimeout(this.runImmediately_.bind(this), | |
| 234 this.delay_ + 1); | |
| 235 this.lastRunTime_ = Date.now(); | |
| 236 return; | |
| 237 } | |
| 238 | |
| 239 // Otherwise, run immediately. | |
| 240 this.runImmediately_(); | |
| 241 }; | |
| 242 | |
| 243 /** | |
| 244 * Calls the schedule immediately and cancels any scheduled calls. | |
| 245 * @private | |
| 246 */ | |
| 247 AsyncUtil.Aggregation.prototype.runImmediately_ = function() { | |
| 248 this.cancelScheduledRuns_(); | |
| 249 this.closure_(); | |
| 250 this.lastRunTime_ = Date.now(); | |
| 251 }; | |
| 252 | |
| 253 /** | |
| 254 * Cancels all scheduled runs (if any). | |
| 255 * @private | |
| 256 */ | |
| 257 AsyncUtil.Aggregation.prototype.cancelScheduledRuns_ = function() { | |
| 258 if (this.scheduledRunsTimer_) { | |
| 259 clearTimeout(this.scheduledRunsTimer_); | |
| 260 this.scheduledRunsTimer_ = null; | |
| 261 } | |
| 262 }; | |
| OLD | NEW |