Home | History | Annotate | Download | only in js
      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  * Creates a class for executing several asynchronous closures in a fifo queue.
     14  * Added tasks will be executed sequentially in order they were added.
     15  *
     16  * @constructor
     17  */
     18 AsyncUtil.Queue = function() {
     19   this.running_ = false;
     20   this.closures_ = [];
     21 };
     22 
     23 /**
     24  * Enqueues a closure to be executed.
     25  * @param {function(function())} closure Closure with a completion callback to
     26  *     be executed.
     27  */
     28 AsyncUtil.Queue.prototype.run = function(closure) {
     29   this.closures_.push(closure);
     30   if (!this.running_)
     31     this.continue_();
     32 };
     33 
     34 /**
     35  * Serves the next closure from the queue.
     36  * @private
     37  */
     38 AsyncUtil.Queue.prototype.continue_ = function() {
     39   if (!this.closures_.length) {
     40     this.running_ = false;
     41     return;
     42   }
     43 
     44   // Run the next closure.
     45   this.running_ = true;
     46   var closure = this.closures_.shift();
     47   closure(this.continue_.bind(this));
     48 };
     49 
     50 /**
     51  * Creates a class for executing several asynchronous closures in a group in
     52  * a dependency order.
     53  *
     54  * @constructor
     55  */
     56 AsyncUtil.Group = function() {
     57   this.addedTasks_ = {};
     58   this.pendingTasks_ = {};
     59   this.finishedTasks_ = {};
     60   this.completionCallbacks_ = [];
     61 };
     62 
     63 /**
     64  * Enqueues a closure to be executed after dependencies are completed.
     65  *
     66  * @param {function(function())} closure Closure with a completion callback to
     67  *     be executed.
     68  * @param {Array.<string>=} opt_dependencies Array of dependencies. If no
     69  *     dependencies, then the the closure will be executed immediately.
     70  * @param {string=} opt_name Task identifier. Specify to use in dependencies.
     71  */
     72 AsyncUtil.Group.prototype.add = function(closure, opt_dependencies, opt_name) {
     73   var length = Object.keys(this.addedTasks_).length;
     74   var name = opt_name || ('(unnamed#' + (length + 1) + ')');
     75 
     76   var task = {
     77     closure: closure,
     78     dependencies: opt_dependencies || [],
     79     name: name
     80   };
     81 
     82   this.addedTasks_[name] = task;
     83   this.pendingTasks_[name] = task;
     84 };
     85 
     86 /**
     87  * Runs the enqueued closured in order of dependencies.
     88  *
     89  * @param {function()=} opt_onCompletion Completion callback.
     90  */
     91 AsyncUtil.Group.prototype.run = function(opt_onCompletion) {
     92   if (opt_onCompletion)
     93     this.completionCallbacks_.push(opt_onCompletion);
     94   this.continue_();
     95 };
     96 
     97 /**
     98  * Runs enqueued pending tasks whose dependencies are completed.
     99  * @private
    100  */
    101 AsyncUtil.Group.prototype.continue_ = function() {
    102   // If all of the added tasks have finished, then call completion callbacks.
    103   if (Object.keys(this.addedTasks_).length ==
    104       Object.keys(this.finishedTasks_).length) {
    105     for (var index = 0; index < this.completionCallbacks_.length; index++) {
    106       var callback = this.completionCallbacks_[index];
    107       callback();
    108     }
    109     this.completionCallbacks_ = [];
    110     return;
    111   }
    112 
    113   for (var name in this.pendingTasks_) {
    114     var task = this.pendingTasks_[name];
    115     var dependencyMissing = false;
    116     for (var index = 0; index < task.dependencies.length; index++) {
    117       var dependency = task.dependencies[index];
    118       // Check if the dependency has finished.
    119       if (!this.finishedTasks_[dependency])
    120         dependencyMissing = true;
    121     }
    122     // All dependences finished, therefore start the task.
    123     if (!dependencyMissing) {
    124       delete this.pendingTasks_[task.name];
    125       task.closure(this.finish_.bind(this, task));
    126     }
    127   }
    128 };
    129 
    130 /**
    131  * Finishes the passed task and continues executing enqueued closures.
    132  *
    133  * @param {Object} task Task object.
    134  * @private
    135  */
    136 AsyncUtil.Group.prototype.finish_ = function(task) {
    137   this.finishedTasks_[task.name] = task;
    138   this.continue_();
    139 };
    140 
    141 /**
    142  * Aggregates consecutive calls and executes the closure only once instead of
    143  * several times. The first call is always called immediately, and the next
    144  * consecutive ones are aggregated and the closure is called only once once
    145  * |delay| amount of time passes after the last call to run().
    146  *
    147  * @param {function()} closure Closure to be aggregated.
    148  * @param {number=} opt_delay Minimum aggregation time in milliseconds. Default
    149  *     is 50 milliseconds.
    150  * @constructor
    151  */
    152 AsyncUtil.Aggregation = function(closure, opt_delay) {
    153   /**
    154    * @type {number}
    155    * @private
    156    */
    157   this.delay_ = opt_delay || 50;
    158 
    159   /**
    160    * @type {function()}
    161    * @private
    162    */
    163   this.closure_ = closure;
    164 
    165   /**
    166    * @type {number?}
    167    * @private
    168    */
    169   this.scheduledRunsTimer_ = null;
    170 
    171   /**
    172    * @type {number}
    173    * @private
    174    */
    175   this.lastRunTime_ = 0;
    176 };
    177 
    178 /**
    179  * Runs a closure. Skips consecutive calls. The first call is called
    180  * immediately.
    181  */
    182 AsyncUtil.Aggregation.prototype.run = function() {
    183   // If recently called, then schedule the consecutive call with a delay.
    184   if (Date.now() - this.lastRunTime_ < this.delay_) {
    185     this.cancelScheduledRuns_();
    186     this.scheduledRunsTimer_ = setTimeout(this.runImmediately_.bind(this),
    187                                           this.delay_ + 1);
    188     this.lastRunTime_ = Date.now();
    189     return;
    190   }
    191 
    192   // Otherwise, run immediately.
    193   this.runImmediately_();
    194 };
    195 
    196 /**
    197  * Calls the schedule immediately and cancels any scheduled calls.
    198  * @private
    199  */
    200 AsyncUtil.Aggregation.prototype.runImmediately_ = function() {
    201   this.cancelScheduledRuns_();
    202   this.closure_();
    203   this.lastRunTime_ = Date.now();
    204 };
    205 
    206 /**
    207  * Cancels all scheduled runs (if any).
    208  * @private
    209  */
    210 AsyncUtil.Aggregation.prototype.cancelScheduledRuns_ = function() {
    211   if (this.scheduledRunsTimer_) {
    212     clearTimeout(this.scheduledRunsTimer_);
    213     this.scheduledRunsTimer_ = null;
    214   }
    215 };
    216