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.
8 * Namespace for async utility functions.
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
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
23 * @param {function()} completionCallback Called when all iterations are
25 * @param {Object=} opt_thisObject Bound to callback if given.
28 AsyncUtil.forEach = function(
29 array, callback, completionCallback, opt_thisObject) {
31 callback = callback.bind(opt_thisObject);
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));
39 queue.run(function(iterationCompletionCallback) {
40 completionCallback(); // Don't pass iteration completion callback.
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.
50 AsyncUtil.Queue = function() {
51 this.running_ = false;
56 * @return {boolean} True when a task is running, otherwise false.
58 AsyncUtil.Queue.prototype.isRunning = function() {
63 * Enqueues a closure to be executed.
64 * @param {function(function())} closure Closure with a completion callback to
67 AsyncUtil.Queue.prototype.run = function(closure) {
68 this.closures_.push(closure);
74 * Serves the next closure from the queue.
77 AsyncUtil.Queue.prototype.continue_ = function() {
78 if (!this.closures_.length) {
79 this.running_ = false;
83 // Run the next closure.
85 var closure = this.closures_.shift();
86 closure(this.continue_.bind(this));
90 * Cancels all pending tasks. Note that this does NOT cancel the task running
93 AsyncUtil.Queue.prototype.cancel = function() {
98 * Creates a class for executing several asynchronous closures in a group in
103 AsyncUtil.Group = function() {
104 this.addedTasks_ = {};
105 this.pendingTasks_ = {};
106 this.finishedTasks_ = {};
107 this.completionCallbacks_ = [];
111 * Enqueues a closure to be executed after dependencies are completed.
113 * @param {function(function())} closure Closure with a completion callback to
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.
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) + ')');
125 dependencies: opt_dependencies || [],
129 this.addedTasks_[name] = task;
130 this.pendingTasks_[name] = task;
134 * Runs the enqueued closured in order of dependencies.
136 * @param {function()=} opt_onCompletion Completion callback.
138 AsyncUtil.Group.prototype.run = function(opt_onCompletion) {
139 if (opt_onCompletion)
140 this.completionCallbacks_.push(opt_onCompletion);
145 * Runs enqueued pending tasks whose dependencies are completed.
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];
156 this.completionCallbacks_ = [];
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;
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));
178 * Finishes the passed task and continues executing enqueued closures.
180 * @param {Object} task Task object.
183 AsyncUtil.Group.prototype.finish_ = function(task) {
184 this.finishedTasks_[task.name] = task;
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().
194 * @param {function()} closure Closure to be aggregated.
195 * @param {number=} opt_delay Minimum aggregation time in milliseconds. Default
196 * is 50 milliseconds.
199 AsyncUtil.Aggregation = function(closure, opt_delay) {
204 this.delay_ = opt_delay || 50;
210 this.closure_ = closure;
216 this.scheduledRunsTimer_ = null;
222 this.lastRunTime_ = 0;
226 * Runs a closure. Skips consecutive calls. The first call is called
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),
235 this.lastRunTime_ = Date.now();
239 // Otherwise, run immediately.
240 this.runImmediately_();
244 * Calls the schedule immediately and cancels any scheduled calls.
247 AsyncUtil.Aggregation.prototype.runImmediately_ = function() {
248 this.cancelScheduledRuns_();
250 this.lastRunTime_ = Date.now();
254 * Cancels all scheduled runs (if any).
257 AsyncUtil.Aggregation.prototype.cancelScheduledRuns_ = function() {
258 if (this.scheduledRunsTimer_) {
259 clearTimeout(this.scheduledRunsTimer_);
260 this.scheduledRunsTimer_ = null;