123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- // Copyright 2006 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 A timer class to which other classes and objects can listen on.
- * This is only an abstraction above {@code setInterval}.
- *
- * @see ../demos/timers.html
- */
- goog.provide('goog.Timer');
- goog.require('goog.Promise');
- goog.require('goog.events.EventTarget');
- /**
- * Class for handling timing events.
- *
- * @param {number=} opt_interval Number of ms between ticks (default: 1ms).
- * @param {Object=} opt_timerObject An object that has {@code setTimeout},
- * {@code setInterval}, {@code clearTimeout} and {@code clearInterval}
- * (e.g., {@code window}).
- * @constructor
- * @extends {goog.events.EventTarget}
- */
- goog.Timer = function(opt_interval, opt_timerObject) {
- goog.events.EventTarget.call(this);
- /**
- * Number of ms between ticks
- * @private {number}
- */
- this.interval_ = opt_interval || 1;
- /**
- * An object that implements {@code setTimeout}, {@code setInterval},
- * {@code clearTimeout} and {@code clearInterval}. We default to the window
- * object. Changing this on {@link goog.Timer.prototype} changes the object
- * for all timer instances which can be useful if your environment has some
- * other implementation of timers than the {@code window} object.
- * @private {{setTimeout:!Function, clearTimeout:!Function}}
- */
- this.timerObject_ = /** @type {{setTimeout, clearTimeout}} */ (
- opt_timerObject || goog.Timer.defaultTimerObject);
- /**
- * Cached {@code tick_} bound to the object for later use in the timer.
- * @private {Function}
- * @const
- */
- this.boundTick_ = goog.bind(this.tick_, this);
- /**
- * Firefox browser often fires the timer event sooner (sometimes MUCH sooner)
- * than the requested timeout. So we compare the time to when the event was
- * last fired, and reschedule if appropriate. See also
- * {@link goog.Timer.intervalScale}.
- * @private {number}
- */
- this.last_ = goog.now();
- };
- goog.inherits(goog.Timer, goog.events.EventTarget);
- /**
- * Maximum timeout value.
- *
- * Timeout values too big to fit into a signed 32-bit integer may cause overflow
- * in FF, Safari, and Chrome, resulting in the timeout being scheduled
- * immediately. It makes more sense simply not to schedule these timeouts, since
- * 24.8 days is beyond a reasonable expectation for the browser to stay open.
- *
- * @private {number}
- * @const
- */
- goog.Timer.MAX_TIMEOUT_ = 2147483647;
- /**
- * A timer ID that cannot be returned by any known implementation of
- * {@code window.setTimeout}. Passing this value to {@code window.clearTimeout}
- * should therefore be a no-op.
- *
- * @private {number}
- * @const
- */
- goog.Timer.INVALID_TIMEOUT_ID_ = -1;
- /**
- * Whether this timer is enabled
- * @type {boolean}
- */
- goog.Timer.prototype.enabled = false;
- /**
- * An object that implements {@code setTimeout}, {@code setInterval},
- * {@code clearTimeout} and {@code clearInterval}. We default to the global
- * object. Changing {@code goog.Timer.defaultTimerObject} changes the object for
- * all timer instances which can be useful if your environment has some other
- * implementation of timers you'd like to use.
- * @type {{setTimeout, clearTimeout}}
- */
- goog.Timer.defaultTimerObject = goog.global;
- /**
- * Variable that controls the timer error correction. If the timer is called
- * before the requested interval times {@code intervalScale}, which often
- * happens on Mozilla, the timer is rescheduled.
- * @see {@link #last_}
- * @type {number}
- */
- goog.Timer.intervalScale = 0.8;
- /**
- * Variable for storing the result of {@code setInterval}.
- * @private {?number}
- */
- goog.Timer.prototype.timer_ = null;
- /**
- * Gets the interval of the timer.
- * @return {number} interval Number of ms between ticks.
- */
- goog.Timer.prototype.getInterval = function() {
- return this.interval_;
- };
- /**
- * Sets the interval of the timer.
- * @param {number} interval Number of ms between ticks.
- */
- goog.Timer.prototype.setInterval = function(interval) {
- this.interval_ = interval;
- if (this.timer_ && this.enabled) {
- // Stop and then start the timer to reset the interval.
- this.stop();
- this.start();
- } else if (this.timer_) {
- this.stop();
- }
- };
- /**
- * Callback for the {@code setTimeout} used by the timer.
- * @private
- */
- goog.Timer.prototype.tick_ = function() {
- if (this.enabled) {
- var elapsed = goog.now() - this.last_;
- if (elapsed > 0 && elapsed < this.interval_ * goog.Timer.intervalScale) {
- this.timer_ = this.timerObject_.setTimeout(
- this.boundTick_, this.interval_ - elapsed);
- return;
- }
- // Prevents setInterval from registering a duplicate timeout when called
- // in the timer event handler.
- if (this.timer_) {
- this.timerObject_.clearTimeout(this.timer_);
- this.timer_ = null;
- }
- this.dispatchTick();
- // The timer could be stopped in the timer event handler.
- if (this.enabled) {
- this.timer_ =
- this.timerObject_.setTimeout(this.boundTick_, this.interval_);
- this.last_ = goog.now();
- }
- }
- };
- /**
- * Dispatches the TICK event. This is its own method so subclasses can override.
- */
- goog.Timer.prototype.dispatchTick = function() {
- this.dispatchEvent(goog.Timer.TICK);
- };
- /**
- * Starts the timer.
- */
- goog.Timer.prototype.start = function() {
- this.enabled = true;
- // If there is no interval already registered, start it now
- if (!this.timer_) {
- // IMPORTANT!
- // window.setInterval in FireFox has a bug - it fires based on
- // absolute time, rather than on relative time. What this means
- // is that if a computer is sleeping/hibernating for 24 hours
- // and the timer interval was configured to fire every 1000ms,
- // then after the PC wakes up the timer will fire, in rapid
- // succession, 3600*24 times.
- // This bug is described here and is already fixed, but it will
- // take time to propagate, so for now I am switching this over
- // to setTimeout logic.
- // https://bugzilla.mozilla.org/show_bug.cgi?id=376643
- //
- this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_);
- this.last_ = goog.now();
- }
- };
- /**
- * Stops the timer.
- */
- goog.Timer.prototype.stop = function() {
- this.enabled = false;
- if (this.timer_) {
- this.timerObject_.clearTimeout(this.timer_);
- this.timer_ = null;
- }
- };
- /** @override */
- goog.Timer.prototype.disposeInternal = function() {
- goog.Timer.superClass_.disposeInternal.call(this);
- this.stop();
- delete this.timerObject_;
- };
- /**
- * Constant for the timer's event type.
- * @const
- */
- goog.Timer.TICK = 'tick';
- /**
- * Calls the given function once, after the optional pause.
- * <p>
- * The function is always called asynchronously, even if the delay is 0. This
- * is a common trick to schedule a function to run after a batch of browser
- * event processing.
- *
- * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function
- * or object that has a handleEvent method.
- * @param {number=} opt_delay Milliseconds to wait; default is 0.
- * @param {SCOPE=} opt_handler Object in whose scope to call the listener.
- * @return {number} A handle to the timer ID.
- * @template SCOPE
- */
- goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {
- if (goog.isFunction(listener)) {
- if (opt_handler) {
- listener = goog.bind(listener, opt_handler);
- }
- } else if (listener && typeof listener.handleEvent == 'function') {
- // using typeof to prevent strict js warning
- listener = goog.bind(listener.handleEvent, listener);
- } else {
- throw Error('Invalid listener argument');
- }
- if (Number(opt_delay) > goog.Timer.MAX_TIMEOUT_) {
- // Timeouts greater than MAX_INT return immediately due to integer
- // overflow in many browsers. Since MAX_INT is 24.8 days, just don't
- // schedule anything at all.
- return goog.Timer.INVALID_TIMEOUT_ID_;
- } else {
- return goog.Timer.defaultTimerObject.setTimeout(listener, opt_delay || 0);
- }
- };
- /**
- * Clears a timeout initiated by {@link #callOnce}.
- * @param {?number} timerId A timer ID.
- */
- goog.Timer.clear = function(timerId) {
- goog.Timer.defaultTimerObject.clearTimeout(timerId);
- };
- /**
- * @param {number} delay Milliseconds to wait.
- * @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value
- * with which the promise will be resolved.
- * @return {!goog.Promise<RESULT>} A promise that will be resolved after
- * the specified delay, unless it is canceled first.
- * @template RESULT
- */
- goog.Timer.promise = function(delay, opt_result) {
- var timerKey = null;
- return new goog
- .Promise(function(resolve, reject) {
- timerKey =
- goog.Timer.callOnce(function() { resolve(opt_result); }, delay);
- if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) {
- reject(new Error('Failed to schedule timer.'));
- }
- })
- .thenCatch(function(error) {
- // Clear the timer. The most likely reason is "cancel" signal.
- goog.Timer.clear(timerKey);
- throw error;
- });
- };
|