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 |