190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// found in the LICENSE file.
490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)'use strict';
690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Namespace for async utility functions.
990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
1090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)var AsyncUtil = {};
1190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * Asynchronous version of Array.forEach.
143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * This executes a provided function callback once per array element, then
153551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * run completionCallback to notify the completion.
163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * The callback can be an asynchronous function, but the execution is
173551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * sequentially done.
183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *
193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @param {Array.<T>} array The array to be iterated.
203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @param {function(function(), T, number, Array.<T>} callback The iteration
213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *     callback. The first argument is a callback to notify the completion of
223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *     the iteration.
233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @param {function()} completionCallback Called when all iterations are
243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *     completed.
253551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @param {Object=} opt_thisObject Bound to callback if given.
263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @template T
273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) */
283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)AsyncUtil.forEach = function(
293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    array, callback, completionCallback, opt_thisObject) {
303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (opt_thisObject)
313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    callback = callback.bind(opt_thisObject);
323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  var queue = new AsyncUtil.Queue();
343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  for (var i = 0; i < array.length; i++) {
353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    queue.run(function(element, index, iterationCompletionCallback) {
363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      callback(iterationCompletionCallback, element, index, array);
373551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    }.bind(null, array[i], i));
383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  queue.run(function(iterationCompletionCallback) {
403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    completionCallback();  // Don't pass iteration completion callback.
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  });
423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)};
433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)/**
4590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Creates a class for executing several asynchronous closures in a fifo queue.
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Added tasks will be started in order they were added. Tasks are run
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * concurrently. At most, |limit| jobs will be run at the same time.
4890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} limit The number of jobs to run at the same time.
5090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @constructor
5190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue = function(limit) {
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  console.assert(limit > 0, '|limit| must be larger than 0');
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.limit_ = limit;
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.addedTasks_ = [];
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTasks_ = [];
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.isCancelled_ = false;
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Object.seal(this);
6190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
6290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
6390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) * @return {boolean} True when a task is running, otherwise false.
654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) */
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.isRunning = function() {
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.pendingTasks_.length !== 0;
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {number} Number of waiting tasks.
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.getWaitingTasksCount = function() {
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.addedTasks_.length;
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Number of running tasks.
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.getRunningTasksCount = function() {
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.pendingTasks_.length;
824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)};
834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)/**
8590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Enqueues a closure to be executed.
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {function(function())} closure Closure with a completion
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     callback to be executed.
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.run = function(closure) {
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.isCancelled_) {
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    console.error('Queue is calcelled. Cannot add a new task.');
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.addedTasks_.push(closure);
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.continue_();
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Cancels the queue. It removes all the not-run (yet) tasks. Note that this
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * does NOT stop tasks currently running.
10290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.cancel = function() {
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.isCancelled_ = true;
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.addedTasks_ = [];
10690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
10790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
10890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} True when the queue have been requested to cancel or is
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *      already cancelled. Otherwise false.
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.isCancelled = function() {
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.isCancelled_;
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Runs the next tasks if available.
11890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @private
11990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.continue_ = function() {
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.addedTasks_.length === 0)
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  console.assert(
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.pendingTasks_.length <= this.limit_,
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      'Too many jobs are running (' + this.pendingTasks_.length + ')');
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.pendingTasks_.length >= this.limit_)
12990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return;
13090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
13190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // Run the next closure.
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var closure = this.addedTasks_.shift();
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTasks_.push(closure);
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  closure(this.onTaskFinished_.bind(this, closure));
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.continue_();
13790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
13890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
13990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Called when a task is finished. Removes the tasks from pending task list.
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {function()} closure Finished task, which has been bound in
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     |continue_|.
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
1444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) */
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.ConcurrentQueue.prototype.onTaskFinished_ = function(closure) {
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var index = this.pendingTasks_.indexOf(closure);
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  console.assert(index >= 0, 'Invalid task is finished');
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTasks_.splice(index, 1);
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.continue_();
1514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)};
1524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)/**
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Creates a class for executing several asynchronous closures in a fifo queue.
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Added tasks will be executed sequentially in order they were added.
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @extends {AsyncUtil.ConcurrentQueue}
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)AsyncUtil.Queue = function() {
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  AsyncUtil.ConcurrentQueue.call(this, 1);
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAsyncUtil.Queue.prototype = {
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  __proto__: AsyncUtil.ConcurrentQueue.prototype
1661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci};
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
16990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Creates a class for executing several asynchronous closures in a group in
17090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * a dependency order.
17190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *
17290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @constructor
17390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
17490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)AsyncUtil.Group = function() {
17590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.addedTasks_ = {};
17690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.pendingTasks_ = {};
17790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.finishedTasks_ = {};
17890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.completionCallbacks_ = [];
17990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
18090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
18190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
18290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Enqueues a closure to be executed after dependencies are completed.
18390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *
18490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {function(function())} closure Closure with a completion callback to
18590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *     be executed.
18690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {Array.<string>=} opt_dependencies Array of dependencies. If no
18790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *     dependencies, then the the closure will be executed immediately.
18890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {string=} opt_name Task identifier. Specify to use in dependencies.
18990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
19090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)AsyncUtil.Group.prototype.add = function(closure, opt_dependencies, opt_name) {
19190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  var length = Object.keys(this.addedTasks_).length;
19290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  var name = opt_name || ('(unnamed#' + (length + 1) + ')');
19390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
19490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  var task = {
19590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    closure: closure,
19690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    dependencies: opt_dependencies || [],
19790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    name: name
19890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  };
19990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
20090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.addedTasks_[name] = task;
20190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.pendingTasks_[name] = task;
20290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
20390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
20490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
20590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Runs the enqueued closured in order of dependencies.
20690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *
20790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {function()=} opt_onCompletion Completion callback.
20890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
20990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)AsyncUtil.Group.prototype.run = function(opt_onCompletion) {
21090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (opt_onCompletion)
21190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    this.completionCallbacks_.push(opt_onCompletion);
21290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.continue_();
21390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
21490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
21590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
21690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Runs enqueued pending tasks whose dependencies are completed.
21790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @private
21890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
21990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)AsyncUtil.Group.prototype.continue_ = function() {
22090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // If all of the added tasks have finished, then call completion callbacks.
22190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (Object.keys(this.addedTasks_).length ==
22290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      Object.keys(this.finishedTasks_).length) {
22390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    for (var index = 0; index < this.completionCallbacks_.length; index++) {
22490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      var callback = this.completionCallbacks_[index];
22590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      callback();
22690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
22790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    this.completionCallbacks_ = [];
22890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return;
22990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
23090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
23190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  for (var name in this.pendingTasks_) {
23290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    var task = this.pendingTasks_[name];
23390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    var dependencyMissing = false;
23490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    for (var index = 0; index < task.dependencies.length; index++) {
23590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      var dependency = task.dependencies[index];
23690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      // Check if the dependency has finished.
23790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      if (!this.finishedTasks_[dependency])
23890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        dependencyMissing = true;
23990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
24090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    // All dependences finished, therefore start the task.
24190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    if (!dependencyMissing) {
24290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      delete this.pendingTasks_[task.name];
24390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      task.closure(this.finish_.bind(this, task));
24490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
24590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
24690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
24790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
24890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
24990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Finishes the passed task and continues executing enqueued closures.
25090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *
25190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {Object} task Task object.
25290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @private
25390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
25490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)AsyncUtil.Group.prototype.finish_ = function(task) {
25590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.finishedTasks_[task.name] = task;
25690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  this.continue_();
25790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)};
258868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
259868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)/**
2601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Aggregates consecutive calls and executes the closure only once instead of
2611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * several times. The first call is always called immediately, and the next
2621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * consecutive ones are aggregated and the closure is called only once once
2631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * |delay| amount of time passes after the last call to run().
2641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci *
2651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {function()} closure Closure to be aggregated.
2661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {number=} opt_delay Minimum aggregation time in milliseconds. Default
2671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci *     is 50 milliseconds.
2681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @constructor
2691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */
2701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAsyncUtil.Aggregator = function(closure, opt_delay) {
2711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  /**
2721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @type {number}
2731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @private
2741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   */
2751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.delay_ = opt_delay || 50;
2761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  /**
2781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @type {function()}
2791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @private
2801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   */
2811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.closure_ = closure;
2821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  /**
2841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @type {number?}
2851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @private
2861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   */
2871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.scheduledRunsTimer_ = null;
2881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  /**
2901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @type {number}
2911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   * @private
2921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci   */
2931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.lastRunTime_ = 0;
2941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci};
2951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/**
2971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Runs a closure. Skips consecutive calls. The first call is called
2981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * immediately.
2991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */
3001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAsyncUtil.Aggregator.prototype.run = function() {
3011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // If recently called, then schedule the consecutive call with a delay.
3021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (Date.now() - this.lastRunTime_ < this.delay_) {
3031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.cancelScheduledRuns_();
3041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.scheduledRunsTimer_ = setTimeout(this.runImmediately_.bind(this),
3051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                          this.delay_ + 1);
3061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.lastRunTime_ = Date.now();
3071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return;
3081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
3091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Otherwise, run immediately.
3111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.runImmediately_();
3121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci};
3131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/**
3151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Calls the schedule immediately and cancels any scheduled calls.
3161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @private
3171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */
3181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAsyncUtil.Aggregator.prototype.runImmediately_ = function() {
3191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.cancelScheduledRuns_();
3201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.closure_();
3211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.lastRunTime_ = Date.now();
3221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci};
3231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/**
3251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Cancels all scheduled runs (if any).
3261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @private
3271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */
3281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAsyncUtil.Aggregator.prototype.cancelScheduledRuns_ = function() {
3291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (this.scheduledRunsTimer_) {
3301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    clearTimeout(this.scheduledRunsTimer_);
3311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.scheduledRunsTimer_ = null;
3321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
3331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci};
3341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/**
336f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Samples calls so that they are not called too frequently.
337f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * The first call is always called immediately, and the following calls may
338f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * be skipped or delayed to keep each interval no less than |minInterval_|.
339868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) *
340f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {function()} closure Closure to be called.
341f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number=} opt_minInterval Minimum interval between each call in
342f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *     milliseconds. Default is 200 milliseconds.
343868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) * @constructor
344868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) */
345f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)AsyncUtil.RateLimiter = function(closure, opt_minInterval) {
346868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  /**
347f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)   * @type {function()}
348868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   * @private
349868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   */
350f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.closure_ = closure;
351868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
352868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  /**
353f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)   * @type {number}
354868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   * @private
355868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   */
356f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.minInterval_ = opt_minInterval || 200;
357868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
358868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  /**
359f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)   * @type {number}
360868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   * @private
361868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   */
362f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.scheduledRunsTimer_ = 0;
363868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
364868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  /**
365f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)   * This variable remembers the last time the closure is called.
366868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   * @type {number}
367868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   * @private
368868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)   */
369868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  this.lastRunTime_ = 0;
370f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
371f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  Object.seal(this);
372868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)};
373868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
374868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)/**
375f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Requests to run the closure.
376f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Skips or delays calls so that the intervals between calls are no less than
377f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * |minInteval_| milliseconds.
378868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) */
379f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)AsyncUtil.RateLimiter.prototype.run = function() {
380f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var now = Date.now();
381f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // If |minInterval| has not passed since the closure is run, skips or delays
382f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // this run.
383f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (now - this.lastRunTime_ < this.minInterval_) {
384f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    // Delays this run only when there is no scheduled run.
385f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    // Otherwise, simply skip this run.
386f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    if (!this.scheduledRunsTimer_) {
387f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.scheduledRunsTimer_ = setTimeout(
388f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)          this.runImmediately.bind(this),
389f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)          this.lastRunTime_ + this.minInterval_ - now);
390f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    }
391868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return;
392868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
393868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
394f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // Otherwise, run immediately
395f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.runImmediately();
396868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)};
397868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
398868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)/**
399f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Calls the scheduled run immediately and cancels any scheduled calls.
400868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) */
401f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)AsyncUtil.RateLimiter.prototype.runImmediately = function() {
402868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  this.cancelScheduledRuns_();
403868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  this.closure_();
404868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  this.lastRunTime_ = Date.now();
405868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)};
406868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
407868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)/**
408868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) * Cancels all scheduled runs (if any).
409868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) * @private
410868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) */
411f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)AsyncUtil.RateLimiter.prototype.cancelScheduledRuns_ = function() {
412868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (this.scheduledRunsTimer_) {
413868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    clearTimeout(this.scheduledRunsTimer_);
414f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.scheduledRunsTimer_ = 0;
415868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
416868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)};
417