Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / common / js / async_util.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  * 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 };