odoo.define('mail/static/src/utils/timer/timer.js', function (require) { 'use strict'; const { makeDeferred } = require('mail/static/src/utils/deferred/deferred.js'); //------------------------------------------------------------------------------ // Errors //------------------------------------------------------------------------------ /** * List of Timer errors. */ /** * Error when timer has been cleared with `.clear()` or `.reset()`. Used to * let know caller of timer that the countdown has been aborted, which * means the inner function will not be called. Usually caller should just * accept it and kindly treated this error as a polite warning. */ class TimerClearedError extends Error { /** * @override */ constructor(timerId, ...args) { super(...args); this.name = 'TimerClearedError'; this.timerId = timerId; } } //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ /** * This class creates a timer which, when times out, calls a function. * Note that the timer is not started on initialization (@see start method). */ class Timer { /** * @param {Object} env the OWL env * @param {function} onTimeout * @param {integer} duration * @param {Object} [param3={}] * @param {boolean} [param3.silentCancelationErrors=true] if unset, caller * of timer will observe some errors that come from current timer calls * that has been cleared with `.clear()` or `.reset()`. * @see TimerClearedError for when timer has been aborted from `.clear()` * or `.reset()`. */ constructor(env, onTimeout, duration, { silentCancelationErrors = true } = {}) { this.env = env; /** * Determine whether the timer has a pending timeout. */ this.isRunning = false; /** * Duration, in milliseconds, until timer times out and calls the * timeout function. */ this._duration = duration; /** * Determine whether the caller of timer `.start()` and `.reset()` * should observe cancelation errors from `.clear()` or `.reset()`. */ this._hasSilentCancelationErrors = silentCancelationErrors; /** * The function that is called when the timer times out. */ this._onTimeout = onTimeout; /** * Deferred of a currently pending invocation to inner function on * timeout. */ this._timeoutDeferred = undefined; /** * Internal reference of `setTimeout()` that is used to invoke function * when timer times out. Useful to clear it when timer is cleared/reset. */ this._timeoutId = undefined; } //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- /** * Clear the timer, which basically sets the state of timer as if it was * just instantiated, without being started. This function makes sense only * when this timer is running. */ clear() { this.env.browser.clearTimeout(this._timeoutId); this.isRunning = false; if (!this._timeoutDeferred) { return; } this._timeoutDeferred.reject(new TimerClearedError(this.id)); } /** * Reset the timer, i.e. the pending timeout is refreshed with initial * duration. This function makes sense only when this timer is running. */ async reset() { this.clear(); await this.start(); } /** * Starts the timer, i.e. after a certain duration, it times out and calls * a function back. This function makes sense only when this timer is not * yet running. * * @throws {Error} in case the timer is already running. */ async start() { if (this.isRunning) { throw new Error("Cannot start a timer that is currently running."); } this.isRunning = true; const timeoutDeferred = makeDeferred(); this._timeoutDeferred = timeoutDeferred; const timeoutId = this.env.browser.setTimeout( () => { this.isRunning = false; timeoutDeferred.resolve(this._onTimeout()); }, this._duration ); this._timeoutId = timeoutId; let result; try { result = await timeoutDeferred; } catch (error) { if ( !this._hasSilentCancelationErrors || !(error instanceof TimerClearedError) || error.timerId !== this.id ) { // This branching should never happens. // Still defined in case of programming error. throw error; } } finally { this.env.browser.clearTimeout(timeoutId); this._timeoutDeferred = undefined; this.isRunning = false; } return result; } } /** * Make external timer errors accessible from timer class. */ Object.assign(Timer, { TimerClearedError, }); return Timer; });