• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
10var 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 */
28AsyncUtil.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 */
50AsyncUtil.Queue = function() {
51  this.running_ = false;
52  this.closures_ = [];
53};
54
55/**
56 * @return {boolean} True when a task is running, otherwise false.
57 */
58AsyncUtil.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 */
67AsyncUtil.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 */
77AsyncUtil.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 */
93AsyncUtil.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 */
103AsyncUtil.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 */
119AsyncUtil.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 */
138AsyncUtil.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 */
148AsyncUtil.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 */
183AsyncUtil.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 */
199AsyncUtil.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 */
229AsyncUtil.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 */
247AsyncUtil.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 */
257AsyncUtil.Aggregation.prototype.cancelScheduledRuns_ = function() {
258  if (this.scheduledRunsTimer_) {
259    clearTimeout(this.scheduledRunsTimer_);
260    this.scheduledRunsTimer_ = null;
261  }
262};
263