Path: blob/trunk/third_party/closure/goog/timer/timer.js
4503 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview A timer class to which other classes and objects can listen on.8* This is only an abstraction above `setInterval`.9*10* @see ../demos/timers.html11*/1213goog.provide('goog.Timer');1415goog.require('goog.Promise');16goog.require('goog.events.EventTarget');17goog.requireType('goog.Thenable');18192021/**22* Class for handling timing events.23*24* @param {number=} opt_interval Number of ms between ticks (default: 1ms).25* @param {Object=} opt_timerObject An object that has `setTimeout`,26* `setInterval`, `clearTimeout` and `clearInterval`27* (e.g., `window`).28* @constructor29* @extends {goog.events.EventTarget}30*/31goog.Timer = function(opt_interval, opt_timerObject) {32'use strict';33goog.events.EventTarget.call(this);3435/**36* Number of ms between ticks37* @private {number}38*/39this.interval_ = opt_interval || 1;4041/**42* An object that implements `setTimeout`, `setInterval`,43* `clearTimeout` and `clearInterval`. We default to the window44* object. Changing this on {@link goog.Timer.prototype} changes the object45* for all timer instances which can be useful if your environment has some46* other implementation of timers than the `window` object.47* @private {{setTimeout:!Function, clearTimeout:!Function}}48*/49this.timerObject_ = /** @type {{setTimeout, clearTimeout}} */ (50opt_timerObject || goog.Timer.defaultTimerObject);5152/**53* Cached `tick_` bound to the object for later use in the timer.54* @private {Function}55* @const56*/57this.boundTick_ = goog.bind(this.tick_, this);5859/**60* Firefox browser often fires the timer event sooner (sometimes MUCH sooner)61* than the requested timeout. So we compare the time to when the event was62* last fired, and reschedule if appropriate. See also63* {@link goog.Timer.intervalScale}.64* @private {number}65*/66this.last_ = goog.now();67};68goog.inherits(goog.Timer, goog.events.EventTarget);697071/**72* Maximum timeout value.73*74* Timeout values too big to fit into a signed 32-bit integer may cause overflow75* in FF, Safari, and Chrome, resulting in the timeout being scheduled76* immediately. It makes more sense simply not to schedule these timeouts, since77* 24.8 days is beyond a reasonable expectation for the browser to stay open.78*79* @private {number}80* @const81*/82goog.Timer.MAX_TIMEOUT_ = 2147483647;838485/**86* A timer ID that cannot be returned by any known implementation of87* `window.setTimeout`. Passing this value to `window.clearTimeout`88* should therefore be a no-op.89*90* @private {number}91* @const92*/93goog.Timer.INVALID_TIMEOUT_ID_ = -1;949596/**97* Whether this timer is enabled98* @type {boolean}99*/100goog.Timer.prototype.enabled = false;101102103/**104* An object that implements `setTimeout`, `setInterval`,105* `clearTimeout` and `clearInterval`. We default to the global106* object. Changing `goog.Timer.defaultTimerObject` changes the object for107* all timer instances which can be useful if your environment has some other108* implementation of timers you'd like to use.109* @type {{setTimeout, clearTimeout}}110*/111goog.Timer.defaultTimerObject = goog.global;112113114/**115* Variable that controls the timer error correction. If the timer is called116* before the requested interval times `intervalScale`, which often117* happens on Mozilla, the timer is rescheduled.118* @see {@link #last_}119* @type {number}120*/121goog.Timer.intervalScale = 0.8;122123124/**125* Variable for storing the result of `setInterval`.126* @private {?number}127*/128goog.Timer.prototype.timer_ = null;129130131/**132* Gets the interval of the timer.133* @return {number} interval Number of ms between ticks.134*/135goog.Timer.prototype.getInterval = function() {136'use strict';137return this.interval_;138};139140141/**142* Sets the interval of the timer.143* @param {number} interval Number of ms between ticks.144*/145goog.Timer.prototype.setInterval = function(interval) {146'use strict';147this.interval_ = interval;148if (this.timer_ && this.enabled) {149// Stop and then start the timer to reset the interval.150this.stop();151this.start();152} else if (this.timer_) {153this.stop();154}155};156157158/**159* Callback for the `setTimeout` used by the timer.160* @private161*/162goog.Timer.prototype.tick_ = function() {163'use strict';164if (this.enabled) {165var elapsed = goog.now() - this.last_;166if (elapsed > 0 && elapsed < this.interval_ * goog.Timer.intervalScale) {167this.timer_ = this.timerObject_.setTimeout(168this.boundTick_, this.interval_ - elapsed);169return;170}171172// Prevents setInterval from registering a duplicate timeout when called173// in the timer event handler.174if (this.timer_) {175this.timerObject_.clearTimeout(this.timer_);176this.timer_ = null;177}178179this.dispatchTick();180// The timer could be stopped in the timer event handler.181if (this.enabled) {182// Stop and start to ensure there is always only one timeout even if183// start is called in the timer event handler.184this.stop();185this.start();186}187}188};189190191/**192* Dispatches the TICK event. This is its own method so subclasses can override.193*/194goog.Timer.prototype.dispatchTick = function() {195'use strict';196this.dispatchEvent(goog.Timer.TICK);197};198199200/**201* Starts the timer.202*/203goog.Timer.prototype.start = function() {204'use strict';205this.enabled = true;206207// If there is no interval already registered, start it now208if (!this.timer_) {209// IMPORTANT!210// window.setInterval in FireFox has a bug - it fires based on211// absolute time, rather than on relative time. What this means212// is that if a computer is sleeping/hibernating for 24 hours213// and the timer interval was configured to fire every 1000ms,214// then after the PC wakes up the timer will fire, in rapid215// succession, 3600*24 times.216// This bug is described here and is already fixed, but it will217// take time to propagate, so for now I am switching this over218// to setTimeout logic.219// https://bugzilla.mozilla.org/show_bug.cgi?id=376643220//221this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_);222this.last_ = goog.now();223}224};225226227/**228* Stops the timer.229*/230goog.Timer.prototype.stop = function() {231'use strict';232this.enabled = false;233if (this.timer_) {234this.timerObject_.clearTimeout(this.timer_);235this.timer_ = null;236}237};238239240/** @override */241goog.Timer.prototype.disposeInternal = function() {242'use strict';243goog.Timer.superClass_.disposeInternal.call(this);244this.stop();245delete this.timerObject_;246};247248249/**250* Constant for the timer's event type.251* @const252*/253goog.Timer.TICK = 'tick';254255256/**257* Calls the given function once, after the optional pause.258* <p>259* The function is always called asynchronously, even if the delay is 0. This260* is a common trick to schedule a function to run after a batch of browser261* event processing.262*263* @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function264* or object that has a handleEvent method.265* @param {number=} opt_delay Milliseconds to wait; default is 0.266* @param {SCOPE=} opt_handler Object in whose scope to call the listener.267* @return {number} A handle to the timer ID.268* @template SCOPE269*/270goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {271'use strict';272if (typeof listener === 'function') {273if (opt_handler) {274listener = goog.bind(listener, opt_handler);275}276} else if (listener && typeof listener.handleEvent == 'function') {277// using typeof to prevent strict js warning278listener = goog.bind(listener.handleEvent, listener);279} else {280throw new Error('Invalid listener argument');281}282283if (Number(opt_delay) > goog.Timer.MAX_TIMEOUT_) {284// Timeouts greater than MAX_INT return immediately due to integer285// overflow in many browsers. Since MAX_INT is 24.8 days, just don't286// schedule anything at all.287return goog.Timer.INVALID_TIMEOUT_ID_;288} else {289return goog.Timer.defaultTimerObject.setTimeout(listener, opt_delay || 0);290}291};292293294/**295* Clears a timeout initiated by {@link #callOnce}.296* @param {?number} timerId A timer ID.297*/298goog.Timer.clear = function(timerId) {299'use strict';300goog.Timer.defaultTimerObject.clearTimeout(timerId);301};302303304/**305* @param {number} delay Milliseconds to wait.306* @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value307* with which the promise will be resolved.308* @return {!goog.Promise<RESULT>} A promise that will be resolved after309* the specified delay, unless it is canceled first.310* @template RESULT311*/312goog.Timer.promise = function(delay, opt_result) {313'use strict';314var timerKey = null;315return new goog316.Promise(function(resolve, reject) {317'use strict';318timerKey = goog.Timer.callOnce(function() {319'use strict';320resolve(opt_result);321}, delay);322if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) {323reject(new Error('Failed to schedule timer.'));324}325})326.thenCatch(function(error) {327'use strict';328// Clear the timer. The most likely reason is "cancel" signal.329goog.Timer.clear(timerKey);330throw error;331});332};333334335