1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines a class useful for handling functions that must be
* invoked later when some condition holds. Examples include deferred function
* calls that return a boolean flag whether it succedeed or not.
*
* Example:
*
* function deferred() {
* var succeeded = false;
* // ... custom code
* return succeeded;
* }
*
* var deferredCall = new goog.async.ConditionalDelay(deferred);
* deferredCall.onSuccess = function() {
* alert('Success: The deferred function has been successfully executed.');
* }
* deferredCall.onFailure = function() {
* alert('Failure: Time limit exceeded.');
* }
*
* // Call the deferred() every 100 msec until it returns true,
* // or 5 seconds pass.
* deferredCall.start(100, 5000);
*
* // Stop the deferred function call (does nothing if it's not active).
* deferredCall.stop();
*
*/
goog.provide('goog.async.ConditionalDelay');
goog.require('goog.Disposable');
goog.require('goog.async.Delay');
/**
* A ConditionalDelay object invokes the associated function after a specified
* interval delay and checks its return value. If the function returns
* {@code true} the conditional delay is cancelled and {@see #onSuccess}
* is called. Otherwise this object keeps to invoke the deferred function until
* either it returns {@code true} or the timeout is exceeded. In the latter case
* the {@see #onFailure} method will be called.
*
* The interval duration and timeout can be specified each time the delay is
* started. Calling start on an active delay will reset the timer.
*
* @param {function():boolean} listener Function to call when the delay
* completes. Should return a value that type-converts to {@code true} if
* the call succeeded and this delay should be stopped.
* @param {Object=} opt_handler The object scope to invoke the function in.
* @constructor
* @struct
* @extends {goog.Disposable}
*/
goog.async.ConditionalDelay = function(listener, opt_handler) {
goog.async.ConditionalDelay.base(this, 'constructor');
/**
* The delay interval in milliseconds to between the calls to the callback.
* Note, that the callback may be invoked earlier than this interval if the
* timeout is exceeded.
* @private {number}
*/
this.interval_ = 0;
/**
* The timeout timestamp until which the delay is to be executed.
* A negative value means no timeout.
* @private {number}
*/
this.runUntil_ = 0;
/**
* True if the listener has been executed, and it returned {@code true}.
* @private {boolean}
*/
this.isDone_ = false;
/**
* The function that will be invoked after a delay.
* @private {function():boolean}
*/
this.listener_ = listener;
/**
* The object context to invoke the callback in.
* @private {Object|undefined}
*/
this.handler_ = opt_handler;
/**
* The underlying goog.async.Delay delegate object.
* @private {goog.async.Delay}
*/
this.delay_ = new goog.async.Delay(
goog.bind(this.onTick_, this), 0 /*interval*/, this /*scope*/);
};
goog.inherits(goog.async.ConditionalDelay, goog.Disposable);
/** @override */
goog.async.ConditionalDelay.prototype.disposeInternal = function() {
this.delay_.dispose();
delete this.listener_;
delete this.handler_;
goog.async.ConditionalDelay.superClass_.disposeInternal.call(this);
};
/**
* Starts the delay timer. The provided listener function will be called
* repeatedly after the specified interval until the function returns
* {@code true} or the timeout is exceeded. Calling start on an active timer
* will stop the timer first.
* @param {number=} opt_interval The time interval between the function
* invocations (in milliseconds). Default is 0.
* @param {number=} opt_timeout The timeout interval (in milliseconds). Takes
* precedence over the {@code opt_interval}, i.e. if the timeout is less
* than the invocation interval, the function will be called when the
* timeout is exceeded. A negative value means no timeout. Default is 0.
*/
goog.async.ConditionalDelay.prototype.start = function(
opt_interval, opt_timeout) {
this.stop();
this.isDone_ = false;
var timeout = opt_timeout || 0;
this.interval_ = Math.max(opt_interval || 0, 0);
this.runUntil_ = timeout < 0 ? -1 : (goog.now() + timeout);
this.delay_.start(
timeout < 0 ? this.interval_ : Math.min(this.interval_, timeout));
};
/**
* Stops the delay timer if it is active. No action is taken if the timer is not
* in use.
*/
goog.async.ConditionalDelay.prototype.stop = function() {
this.delay_.stop();
};
/**
* @return {boolean} True if the delay is currently active, false otherwise.
*/
goog.async.ConditionalDelay.prototype.isActive = function() {
return this.delay_.isActive();
};
/**
* @return {boolean} True if the listener has been executed and returned
* {@code true} since the last call to {@see #start}.
*/
goog.async.ConditionalDelay.prototype.isDone = function() {
return this.isDone_;
};
/**
* Called when the listener has been successfully executed and returned
* {@code true}. The {@see #isDone} method should return {@code true} by now.
* Designed for inheritance, should be overridden by subclasses or on the
* instances if they care.
*/
goog.async.ConditionalDelay.prototype.onSuccess = function() {
// Do nothing by default.
};
/**
* Called when this delayed call is cancelled because the timeout has been
* exceeded, and the listener has never returned {@code true}.
* Designed for inheritance, should be overridden by subclasses or on the
* instances if they care.
*/
goog.async.ConditionalDelay.prototype.onFailure = function() {
// Do nothing by default.
};
/**
* A callback function for the underlying {@code goog.async.Delay} object. When
* executed the listener function is called, and if it returns {@code true}
* the delay is stopped and the {@see #onSuccess} method is invoked.
* If the timeout is exceeded the delay is stopped and the
* {@see #onFailure} method is called.
* @private
*/
goog.async.ConditionalDelay.prototype.onTick_ = function() {
var successful = this.listener_.call(this.handler_);
if (successful) {
this.isDone_ = true;
this.onSuccess();
} else {
// Try to reschedule the task.
if (this.runUntil_ < 0) {
// No timeout.
this.delay_.start(this.interval_);
} else {
var timeLeft = this.runUntil_ - goog.now();
if (timeLeft <= 0) {
this.onFailure();
} else {
this.delay_.start(Math.min(this.interval_, timeLeft));
}
}
}
};
|