UNPKG

rxjs-zone-less

Version:

A set of wrappers for RxJS to avoid unnecessary change detection and zone interference in Angular.

1,162 lines (1,137 loc) 57.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('rxjs'), require('tslib'), require('rxjs/operators'), require('rxjs/testing')) : typeof define === 'function' && define.amd ? define(['exports', 'rxjs', 'tslib', 'rxjs/operators', 'rxjs/testing'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["rxjs-zone-less"] = {}, global.rxjs, global.tslib, global.operators, global.testing)); })(this, (function (exports, rxjs, tslib, operators, testing) { 'use strict'; var __globalThis = typeof globalThis !== 'undefined' && globalThis; var __window = typeof window !== 'undefined' && window; var __self = typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope && self; var __global = typeof global !== 'undefined' && global; // Always use __globalThis if available, which is the spec-defined global variable across all // environments, then fallback to __global first, because in Node tests both __global and // __window may be defined and _global should be __global in that case. var _global = __globalThis || __global || __window || __self; var Promise$1 = getZoneUnPatchedApi('Promise'); function setInterval(cb, ms) { if (ms === void 0) { ms = 0; } return getZoneUnPatchedApi('setInterval')(cb, ms); } function clearInterval(id) { return getZoneUnPatchedApi('clearInterval')(id); } function getZoneUnPatchedApi(targetOrName, name) { // If the user has provided the API name as the first argument, for instance: // `const addEventListener = getZoneUnPatchedApi('addEventListener');` // Then we just swap arguments and make `global` or `window` as the default target. if (typeof targetOrName === 'string') { name = targetOrName; targetOrName = _global; } return targetOrName['__zone_symbol__' + name] || targetOrName[name]; } var intervalProvider = { // When accessing the delegate, use the variable rather than `this` so that // the functions can be called without being bound to the provider. setInterval: function (handler, timeout) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } var delegate = intervalProvider.delegate; if (delegate === null || delegate === void 0 ? void 0 : delegate.setInterval) { return delegate.setInterval.apply(delegate, tslib.__spreadArray([handler, timeout], args, false)); } return setInterval(handler, timeout); }, clearInterval: function (handle) { var delegate = intervalProvider.delegate; return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearInterval) || clearInterval)(handle); }, }; /** * A unit of work to be executed in a `scheduler`. An action is typically * created from within a {@link SchedulerLike} and an RxJS user does not need to concern * themselves about creating and manipulating an Action. * * ```ts * class Action<T> extends Subscription { * new (scheduler: Scheduler, work: (state?: T) => void); * schedule(state?: T, delay: number = 0): Subscription; * } * ``` * * @class Action<T> */ var Action = /** @class */ (function (_super) { tslib.__extends(Action, _super); function Action(scheduler, work) { return _super.call(this) || this; } /** * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed * some context object, `state`. May happen at some point in the future, * according to the `delay` parameter, if specified. * @param {T} [state] Some contextual data that the `work` function uses when * called by the Scheduler. * @param {number} [delay] Time to wait before executing the work, where the * time unit is implicit and defined by the Scheduler. * @return {void} */ Action.prototype.schedule = function (state, delay) { return this; }; return Action; }(rxjs.Subscription)); /** * We need this JSDoc comment for affecting ESDoc. * @ignore * @extends {Ignored} */ var AsyncAction = /** @class */ (function (_super) { tslib.__extends(AsyncAction, _super); function AsyncAction(scheduler, work) { var _this = _super.call(this, scheduler, work) || this; _this.scheduler = scheduler; _this.work = work; _this.pending = false; return _this; } AsyncAction.prototype.schedule = function (state, delay) { if (delay === void 0) { delay = 0; } if (this.closed) { return this; } // Always replace the current state with the new state. this.state = state; var id = this.id; var scheduler = this.scheduler; // // Important implementation note: // // Actions only execute once by default, unless rescheduled from within the // scheduled callback. This allows us to implement single and repeat // actions via the same code path, without adding API surface area, as well // as mimic traditional recursion but across asynchronous boundaries. // // However, JS runtimes and timers distinguish between intervals achieved by // serial `setTimeout` calls vs. a single `setInterval` call. An interval of // serial `setTimeout` calls can be individually delayed, which delays // scheduling the next `setTimeout`, and so on. `setInterval` attempts to // guarantee the interval callback will be invoked more precisely to the // interval period, regardless of load. // // Therefore, we use `setInterval` to schedule single and repeat actions. // If the action reschedules itself with the same delay, the interval is not // canceled. If the action doesn't reschedule, or reschedules with a // different delay, the interval will be canceled after scheduled callback // execution. // if (id != null) { this.id = this.recycleAsyncId(scheduler, id, delay); } // Set the pending flag indicating that this action has been scheduled, or // has recursively rescheduled itself. this.pending = true; this.delay = delay; // If this action has already an async Id, don't request a new one. this.id = this.id || this.requestAsyncId(scheduler, this.id, delay); return this; }; AsyncAction.prototype.requestAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay); }; AsyncAction.prototype.recycleAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } // If this action is rescheduled with the same delay time, don't clear the interval id. if (delay != null && this.delay === delay && this.pending === false) { return id; } // Otherwise, if the action's delay time is different from the current delay, // or the action has been rescheduled before it's executed, clear the interval id intervalProvider.clearInterval(id); return undefined; }; /** * Immediately executes this action and the `work` it contains. * @return {any} */ AsyncAction.prototype.execute = function (state, delay) { if (this.closed) { return new Error('executing a cancelled action'); } this.pending = false; var error = this._execute(state, delay); if (error) { return error; } else if (this.pending === false && this.id != null) { // Dequeue if the action didn't reschedule itself. Don't call // unsubscribe(), because the action could reschedule later. // For example: // ``` // scheduler.schedule(function doWork(counter) { // /* ... I'm a busy worker bee ... */ // var originalAction = this; // /* wait 100ms before rescheduling the action */ // setTimeout(function () { // originalAction.schedule(counter + 1); // }, 100); // }, 1000); // ``` this.id = this.recycleAsyncId(this.scheduler, this.id, null); } }; AsyncAction.prototype._execute = function (state, delay) { var errored = false; var errorValue; try { this.work(state); } catch (e) { errored = true; errorValue = (!!e && e) || new Error(e); } if (errored) { this.unsubscribe(); return errorValue; } }; AsyncAction.prototype.unsubscribe = function () { if (!this.closed) { var _a = this, id = _a.id, scheduler = _a.scheduler; var actions = scheduler.actions; this.work = this.state = this.scheduler = null; this.pending = false; // arrRemove if (actions) { var index = actions.indexOf(this); 0 <= index && actions.splice(index, 1); } if (id != null) { this.id = this.recycleAsyncId(scheduler, id, null); } this.delay = null; _super.prototype.unsubscribe.call(this); } }; return AsyncAction; }(Action)); var dateTimestampProvider = { now: function () { return (dateTimestampProvider.delegate || Date).now(); }, }; /** * An execution context and a data structure to order tasks and schedule their * execution. Provides a notion of (potentially virtual) time, through the * `now()` getter method. * * Each unit of work in a Scheduler is called an `Action`. * * ```ts * class Scheduler { * now(): number; * schedule(work, delay?, state?): Subscription; * } * ``` * * @class Scheduler * @deprecated Scheduler is an internal implementation detail of RxJS, and * should not be used directly. Rather, create your own class and implement * {@link SchedulerLike}. Will be made internal in v8. */ var Scheduler = /** @class */ (function () { function Scheduler(schedulerActionCtor, now) { if (now === void 0) { now = Scheduler.now; } this.schedulerActionCtor = schedulerActionCtor; this.now = now; } /** * Schedules a function, `work`, for execution. May happen at some point in * the future, according to the `delay` parameter, if specified. May be passed * some context object, `state`, which will be passed to the `work` function. * * The given arguments will be processed an stored as an Action object in a * queue of actions. * * @param {function(state: ?T): ?Subscription} work A function representing a * task, or some unit of work to be executed by the Scheduler. * @param {number} [delay] Time to wait before executing the work, where the * time unit is implicit and defined by the Scheduler itself. * @param {T} [state] Some contextual data that the `work` function uses when * called by the Scheduler. * @return {Subscription} A subscription in order to be able to unsubscribe * the scheduled work. */ Scheduler.prototype.schedule = function (work, delay, state) { if (delay === void 0) { delay = 0; } return new this.schedulerActionCtor(this, work).schedule(state, delay); }; Scheduler.now = dateTimestampProvider.now; return Scheduler; }()); var AsyncScheduler = /** @class */ (function (_super) { tslib.__extends(AsyncScheduler, _super); function AsyncScheduler(SchedulerAction, now) { if (now === void 0) { now = Scheduler.now; } var _this = _super.call(this, SchedulerAction, now) || this; _this.actions = []; /** * A flag to indicate whether the Scheduler is currently executing a batch of * queued actions. * @type {boolean} * @internal */ _this._active = false; /** * An internal ID used to track the latest asynchronous task such as those * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and * others. * @type {any} * @internal */ _this._scheduled = undefined; return _this; } AsyncScheduler.prototype.flush = function (action) { var actions = this.actions; if (this._active) { actions.push(action); return; } var error; this._active = true; do { if ((error = action.execute(action.state, action.delay))) { break; } } while ((action = actions.shift())); // exhaust the scheduler queue this._active = false; if (error) { while ((action = actions.shift())) { action.unsubscribe(); } throw error; } }; return AsyncScheduler; }(Scheduler)); /** * * NOTE: This is a zone un-patched version of rxjs asyncScheduler * * Async Scheduler * * <span class="informal">Schedule task as if you used setTimeout(task, duration)</span> * * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating * in intervals. * * If you just want to "defer" task, that is to perform it right after currently * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`), * better choice will be the {@link asapScheduler} scheduler. * * ## Examples * Use async scheduler to delay task * ```ts * import { asyncScheduler } from '@cu/perf-utils'; * * const task = () => console.log('it works!'); * * asyncScheduler.schedule(task, 2000); * * // After 2 seconds logs: * // "it works!" * ``` * * Use async scheduler to repeat task in intervals * ```ts * import { asyncScheduler } from '@cu/perf-utils'; * * function task(state) { * console.log(state); * this.schedule(state + 1, 1000); // `this` references currently executing Action, * // which we reschedule with new state and delay * } * * asyncScheduler.schedule(task, 3000, 0); * * // Logs: * // 0 after 3s * // 1 after 4s * // 2 after 5s * // 3 after 6s * ``` */ var asyncScheduler = new AsyncScheduler(AsyncAction); function isNumeric(val) { // parseFloat NaNs numeric-cast false positives (null|true|false|"") // ...but misinterprets leading-number strings, particularly hex literals ("0x...") // subtraction forces infinities to NaN // adding 1 corrects loss of precision from parseFloat (#15100) return !Array.isArray(val) && val - parseFloat(val) + 1 >= 0; } function isScheduler(value) { return value && typeof value.schedule === 'function'; } /** * Creates an Observable that emits sequential numbers every specified * interval of time, on a specified {@link SchedulerLike}. * * <span class="informal">Emits incremental numbers periodically in time. * </span> * * ![](interval.png) * * `interval` returns an Observable that emits an infinite sequence of * ascending integers, with a constant interval of time of your choosing * between those emissions. The first emission is not sent immediately, but * only after the first period has passed. By default, this operator uses the * `async` {@link SchedulerLike} to provide a notion of time, but you may pass any * {@link SchedulerLike} to it. * * ## Example * Emits ascending numbers, one every second (1000ms) up to the number 3 * ```ts * import { interval } from 'rxjs'; * import { take } from 'rxjs/operators'; * * const numbers = interval(1000); * * const takeFourNumbers = numbers.pipe(take(4)); * * takeFourNumbers.subscribe(x => console.log('Next: ', x)); * * // Logs: * // Next: 0 * // Next: 1 * // Next: 2 * // Next: 3 * ``` * * @see {@link timer} * @see {@link delay} * * @param {number} [period=0] The interval size in milliseconds (by default) * or the time unit determined by the scheduler's clock. * @param {SchedulerLike} [scheduler=async] The {@link SchedulerLike} to use for scheduling * the emission of values, and providing a notion of "time". * @return {Observable} An Observable that emits a sequential number each time * interval. * @static true * @name interval * @owner Observable */ function interval(period, scheduler) { if (period === void 0) { period = 0; } if (scheduler === void 0) { scheduler = asyncScheduler; } if (!isNumeric(period) || period < 0) { period = 0; } if (!scheduler || typeof scheduler.schedule !== 'function') { scheduler = asyncScheduler; } return new rxjs.Observable(function (subscriber) { subscriber.add(scheduler.schedule(dispatch$1, period, { subscriber: subscriber, counter: 0, period: period })); return subscriber; }); } function dispatch$1(state) { var subscriber = state.subscriber, counter = state.counter, period = state.period; subscriber.next(counter); this.schedule({ subscriber: subscriber, counter: counter + 1, period: period }, period); } /** * Creates an Observable that starts emitting after an `dueTime` and * emits ever increasing numbers after each `period` of time thereafter. * * <span class="informal">Its like {@link index/interval}, but you can specify when * should the emissions start.</span> * * ![](timer.png) * * `timer` returns an Observable that emits an infinite sequence of ascending * integers, with a constant interval of time, `period` of your choosing * between those emissions. The first emission happens after the specified * `dueTime`. The initial delay may be a `Date`. By default, this * operator uses the {@link asyncScheduler} {@link SchedulerLike} to provide a notion of time, but you * may pass any {@link SchedulerLike} to it. If `period` is not specified, the output * Observable emits only one value, `0`. Otherwise, it emits an infinite * sequence. * * ## Examples * ### Emits ascending numbers, one every second (1000ms), starting after 3 seconds * ```ts * import { timer } from 'rxjs'; * * const numbers = timer(3000, 1000); * numbers.subscribe(x => console.log(x)); * ``` * * ### Emits one number after five seconds * ```ts * import { timer } from 'rxjs'; * * const numbers = timer(5000); * numbers.subscribe(x => console.log(x)); * ``` * @see {@link index/interval} * @see {@link delay} * * @param {number|Date} [dueTime] The initial delay time specified as a Date object or as an integer denoting * milliseconds to wait before emitting the first value of 0`. * @param {number|SchedulerLike} [periodOrScheduler] The period of time between emissions of the * subsequent numbers. * @param {SchedulerLike} [scheduler=async] The {@link SchedulerLike} to use for scheduling * the emission of values, and providing a notion of "time". * @return {Observable} An Observable that emits a `0` after the * `dueTime` and ever increasing numbers after each `period` of time * thereafter. * @static true * @name timer * @owner Observable */ function timer(dueTime, periodOrScheduler, scheduler) { if (dueTime === void 0) { dueTime = 0; } var period = -1; if (isNumeric(periodOrScheduler)) { period = (Number(periodOrScheduler) < 1 && 1) || Number(periodOrScheduler); } else if (isScheduler(periodOrScheduler)) { scheduler = periodOrScheduler; } if (!isScheduler(scheduler)) { scheduler = asyncScheduler; } return new rxjs.Observable(function (subscriber) { var due = isNumeric(dueTime) ? dueTime : +dueTime - scheduler.now(); return scheduler.schedule(dispatch, due, { index: 0, period: period, subscriber: subscriber, }); }); } function dispatch(state) { var index = state.index, period = state.period, subscriber = state.subscriber; subscriber.next(index); if (subscriber.closed) { return; } else if (period === -1) { return subscriber.complete(); } state.index = index + 1; this.schedule(state, period); } // @ts-ignore function isFunction(fn) { return typeof fn === 'function'; } var isArray = Array.isArray; function fromEvent(target, eventName, options, resultSelector) { if (isFunction(options)) { // DEPRECATED PATH // @ts-ignore resultSelector = options; options = undefined; } if (isFunction(resultSelector)) { // DEPRECATED PATH return fromEvent(target, eventName, options).pipe(operators.map(function (args) { return isArray(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); } return new rxjs.Observable(function (subscriber) { function handler(e) { if (arguments.length > 1) { subscriber.next(Array.prototype.slice.call(arguments)); } else { subscriber.next(e); } } setupSubscription(target, eventName, handler, subscriber, options); }); } function setupSubscription(sourceObj, eventName, handler, subscriber, options) { var unsubscribe; if (isEventTarget(sourceObj)) { var source_1 = sourceObj; getZoneUnPatchedApi(sourceObj, 'addEventListener').call(sourceObj, eventName, handler, options); unsubscribe = function () { return getZoneUnPatchedApi(source_1, 'removeEventListener').call(source_1, eventName, handler, options); }; } else if (isJQueryStyleEventEmitter(sourceObj)) { var source_2 = sourceObj; sourceObj.on(eventName, handler); unsubscribe = function () { return source_2.off(eventName, handler); }; } else if (isNodeStyleEventEmitter(sourceObj)) { var source_3 = sourceObj; sourceObj.addListener(eventName, handler); unsubscribe = function () { return source_3.removeListener(eventName, handler); }; } else if (sourceObj && sourceObj.length) { for (var i = 0, len = sourceObj.length; i < len; i++) { setupSubscription(sourceObj[i], eventName, handler, subscriber, options); } } else { throw new TypeError('Invalid event target'); } subscriber.add(unsubscribe); } function isNodeStyleEventEmitter(sourceObj) { return (sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function'); } function isJQueryStyleEventEmitter(sourceObj) { return (sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function'); } function isEventTarget(sourceObj) { return (sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function'); } var nextHandle = 1; // The promise needs to be created lazily otherwise it won't be patched by Zones var resolved; var activeHandles = {}; /** * Finds the handle in the list of active handles, and removes it. * Returns `true` if found, `false` otherwise. Used both to clear * Immediate scheduled tasks, and to identify if a task should be scheduled. */ function findAndClearHandle(handle) { if (handle in activeHandles) { delete activeHandles[handle]; return true; } return false; } /** * Helper functions to schedule and unschedule microtasks. */ var Immediate = { setImmediate: function (cb) { var handle = nextHandle++; activeHandles[handle] = true; if (!resolved) { resolved = Promise$1.resolve(); } resolved.then(function () { return findAndClearHandle(handle) && cb(); }); return handle; }, clearImmediate: function (handle) { findAndClearHandle(handle); }, }; var setImmediate = Immediate.setImmediate, clearImmediate = Immediate.clearImmediate; var immediateProvider = { setImmediate: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var delegate = immediateProvider.delegate; return ((delegate === null || delegate === void 0 ? void 0 : delegate.setImmediate) || setImmediate).apply(void 0, args); }, clearImmediate: function (handle) { var delegate = immediateProvider.delegate; return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearImmediate) || clearImmediate)(handle); }, }; /** * We need this JSDoc comment for affecting ESDoc. * @ignore * @extends {Ignored} */ var AsapAction = /** @class */ (function (_super) { tslib.__extends(AsapAction, _super); function AsapAction(scheduler, work) { var _this = _super.call(this, scheduler, work) || this; _this.scheduler = scheduler; _this.work = work; return _this; } AsapAction.prototype.requestAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } // If delay is greater than 0, request as an async action. if (delay !== null && delay > 0) { return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); } // Push the action to the end of the scheduler queue. scheduler.actions.push(this); // If a microtask has already been scheduled, don't schedule another // one. If a microtask hasn't been scheduled yet, schedule one now. Return // the current scheduled microtask id. return (scheduler._scheduled || (scheduler._scheduled = immediateProvider.setImmediate(scheduler.flush.bind(scheduler, undefined)))); }; AsapAction.prototype.recycleAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } // If delay exists and is greater than 0, or if the delay is null (the // action wasn't rescheduled) but was originally scheduled as an async // action, then recycle as an async action. if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); } // If the scheduler queue is empty, cancel the requested microtask and // set the scheduled flag to undefined so the next AsapAction will schedule // its own. if (!scheduler.actions.some(function (action) { return action.id === id; })) { immediateProvider.clearImmediate(id); scheduler._scheduled = undefined; } // Return undefined so the action knows to request a new async id if it's rescheduled. return undefined; }; return AsapAction; }(AsyncAction)); var AsapScheduler = /** @class */ (function (_super) { tslib.__extends(AsapScheduler, _super); function AsapScheduler() { return _super !== null && _super.apply(this, arguments) || this; } AsapScheduler.prototype.flush = function (action) { this._active = true; // The async id that effects a call to flush is stored in _scheduled. // Before executing an action, it's necessary to check the action's async // id to determine whether it's supposed to be executed in the current // flush. // Previous implementations of this method used a count to determine this, // but that was unsound, as actions that are unsubscribed - i.e. cancelled - // are removed from the actions array and that can shift actions that are // scheduled to be executed in a subsequent flush into positions at which // they are executed within the current flush. var flushId = this._scheduled; this._scheduled = undefined; var actions = this.actions; var error; action = action || actions.shift(); do { if ((error = action.execute(action.state, action.delay))) { break; } } while ((action = actions[0]) && action.id === flushId && actions.shift()); this._active = false; if (error) { while ((action = actions[0]) && action.id === flushId && actions.shift()) { action.unsubscribe(); } throw error; } }; return AsapScheduler; }(AsyncScheduler)); /** * * NOTE: This is a zone un-patched version of rxjs asapScheduler * * Asap Scheduler * * <span class="informal">Perform task as fast as it can be performed asynchronously</span> * * `asap` scheduler behaves the same as {@link asyncScheduler} scheduler when you use it to delay task * in time. If however you set delay to `0`, `asap` will wait for current synchronously executing * code to end and then it will try to execute given task as fast as possible. * * `asap` scheduler will do its best to minimize time between end of currently executing code * and start of scheduled task. This makes it best candidate for performing so called "deferring". * Traditionally this was achieved by calling `setTimeout(deferredTask, 0)`, but that technique involves * some (although minimal) unwanted delay. * * Note that using `asap` scheduler does not necessarily mean that your task will be first to process * after currently executing code. In particular, if some task was also scheduled with `asap` before, * that task will execute first. That being said, if you need to schedule task asynchronously, but * as soon as possible, `asap` scheduler is your best bet. * * ## Example * Compare async and asap scheduler< * ```ts * import { asapScheduler, asyncScheduler } from '@cu/perf-utils'; * * asyncScheduler.schedule(() => console.log('async')); // scheduling 'async' first... * asapScheduler.schedule(() => console.log('asap')); * * // Logs: * // "asap" * // "async" * // ... but 'asap' goes first! * ``` */ var asapScheduler = new AsapScheduler(AsapAction); /** * We need this JSDoc comment for affecting ESDoc. * @ignore * @extends {Ignored} */ var QueueAction = /** @class */ (function (_super) { tslib.__extends(QueueAction, _super); function QueueAction(scheduler, work) { var _this = _super.call(this, scheduler, work) || this; _this.scheduler = scheduler; _this.work = work; return _this; } QueueAction.prototype.schedule = function (state, delay) { if (delay === void 0) { delay = 0; } if (delay > 0) { return _super.prototype.schedule.call(this, state, delay); } this.delay = delay; this.state = state; this.scheduler.flush(this); return this; }; QueueAction.prototype.execute = function (state, delay) { return delay > 0 || this.closed ? _super.prototype.execute.call(this, state, delay) : this._execute(state, delay); }; QueueAction.prototype.requestAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } // If delay exists and is greater than 0, or if the delay is null (the // action wasn't rescheduled) but was originally scheduled as an async // action, then recycle as an async action. if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); } // Otherwise flush the scheduler starting with this action. return scheduler.flush(this); }; return QueueAction; }(AsyncAction)); var QueueScheduler = /** @class */ (function (_super) { tslib.__extends(QueueScheduler, _super); function QueueScheduler() { return _super !== null && _super.apply(this, arguments) || this; } return QueueScheduler; }(AsyncScheduler)); /** * * NOTE: This is a zone un-patched version of rxjs queueScheduler * * Queue Scheduler * * <span class="informal">Put every next task on a queue, instead of executing it immediately</span> * * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler. * * When used without delay, it schedules given task synchronously - executes it right when * it is scheduled. However when called recursively, that is when inside the scheduled task, * another task is scheduled with queue scheduler, instead of executing immediately as well, * that task will be put on a queue and wait for current one to finish. * * This means that when you execute task with `queue` scheduler, you are sure it will end * before any other task scheduled with that scheduler will start. * * ## Examples * Schedule recursively first, then do something * ```ts * import { queueScheduler } from '@cu/perf-utils'; * * queueScheduler.schedule(() => { * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue * * console.log('first'); * }); * * // Logs: * // "first" * // "second" * ``` * * Reschedule itself recursively * ```ts * import { queueScheduler } from '@cu/perf-utils'; * * queueScheduler.schedule(function(state) { * if (state !== 0) { * console.log('before', state); * this.schedule(state - 1); // `this` references currently executing Action, * // which we reschedule with new state * console.log('after', state); * } * }, 0, 3); * * // In scheduler that runs recursively, you would expect: * // "before", 3 * // "before", 2 * // "before", 1 * // "after", 1 * // "after", 2 * // "after", 3 * * // But with queue it logs: * // "before", 3 * // "after", 3 * // "before", 2 * // "after", 2 * // "before", 1 * // "after", 1 * ``` */ var queueScheduler = new QueueScheduler(QueueAction); var cancelAnimationFrame = getZoneUnPatchedApi('cancelAnimationFrame'); var requestAnimationFrame = getZoneUnPatchedApi('requestAnimationFrame'); var animationFrameProvider = { // When accessing the delegate, use the variable rather than `this` so that // the functions can be called without being bound to the provider. schedule: function (callback) { var request = requestAnimationFrame; var cancel = cancelAnimationFrame; var delegate = animationFrameProvider.delegate; if (delegate) { request = delegate.requestAnimationFrame; cancel = delegate.cancelAnimationFrame; } var handle = request(function (timestamp) { // Clear the cancel function. The request has been fulfilled, so // attempting to cancel the request upon unsubscription would be // pointless. cancel = undefined; callback(timestamp); }); return new rxjs.Subscription(function () { return cancel === null || cancel === void 0 ? void 0 : cancel(handle); }); }, requestAnimationFrame: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var delegate = animationFrameProvider.delegate; return ((delegate === null || delegate === void 0 ? void 0 : delegate.requestAnimationFrame) || requestAnimationFrame).apply(void 0, args); }, cancelAnimationFrame: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var delegate = animationFrameProvider.delegate; return ((delegate === null || delegate === void 0 ? void 0 : delegate.cancelAnimationFrame) || cancelAnimationFrame).apply(void 0, args); }, }; var AnimationFrameAction = /** @class */ (function (_super) { tslib.__extends(AnimationFrameAction, _super); function AnimationFrameAction(scheduler, work) { var _this = _super.call(this, scheduler, work) || this; _this.scheduler = scheduler; _this.work = work; return _this; } AnimationFrameAction.prototype.requestAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } // If delay is greater than 0, request as an async action. if (delay !== null && delay > 0) { return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); } // Push the action to the end of the scheduler queue. scheduler.actions.push(this); // If an animation frame has already been requested, don't request another // one. If an animation frame hasn't been requested yet, request one. Return // the current animation frame request id. return (scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(function () { return scheduler.flush(undefined); }))); }; AnimationFrameAction.prototype.recycleAsyncId = function (scheduler, id, delay) { if (delay === void 0) { delay = 0; } // If delay exists and is greater than 0, or if the delay is null (the // action wasn't rescheduled) but was originally scheduled as an async // action, then recycle as an async action. if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) { return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); } // If the scheduler queue is empty, cancel the requested animation frame and // set the scheduled flag to undefined so the next AnimationFrameAction will // request its own. if (!scheduler.actions.some(function (action) { return action.id === id; })) { animationFrameProvider.cancelAnimationFrame(id); scheduler._scheduled = undefined; } // Return undefined so the action knows to request a new async id if it's rescheduled. return undefined; }; return AnimationFrameAction; }(AsyncAction)); var AnimationFrameScheduler = /** @class */ (function (_super) { tslib.__extends(AnimationFrameScheduler, _super); function AnimationFrameScheduler() { return _super !== null && _super.apply(this, arguments) || this; } AnimationFrameScheduler.prototype.flush = function (action) { this._active = true; // The async id that effects a call to flush is stored in _scheduled. // Before executing an action, it's necessary to check the action's async // id to determine whether it's supposed to be executed in the current // flush. // Previous implementations of this method used a count to determine this, // but that was unsound, as actions that are unsubscribed - i.e. cancelled - // are removed from the actions array and that can shift actions that are // scheduled to be executed in a subsequent flush into positions at which // they are executed within the current flush. var flushId = this._scheduled; this._scheduled = undefined; var actions = this.actions; var error; action = action || actions.shift(); do { if ((error = action.execute(action.state, action.delay))) { break; } } while ((action = actions[0]) && action.id === flushId && actions.shift()); this._active = false; if (error) { while ((action = actions[0]) && action.id === flushId && actions.shift()) { action.unsubscribe(); } throw error; } }; return AnimationFrameScheduler; }(AsyncScheduler)); /** * * NOTE: This is a zone un-patched version of rxjs animationFrameScheduler * * Animation Frame Scheduler * * <span class="informal">Perform task when `window.requestAnimationFrame` would fire</span> * * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler * behaviour. * * Without delay, `animationFrame` scheduler can be used to create smooth browser animations. * It makes sure scheduled task will happen just before next browser content repaint, * thus performing animations as efficiently as possible. * * ## Example * Schedule div height animation * ```ts * // html: <div style="background: #0ff;"></div> * import { animationFrameScheduler } from 'zone-less-rxjs'; * * const div = document.querySelector('div'); * * animationFrameScheduler.schedule(function(height) { * div.style.height = height + "px"; * * this.schedule(height + 1); // `this` references currently executing Action, * // which we reschedule with new state * }, 0, 0); * * // You will see a div element growing in height * ``` */ var animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction); var RxTestScheduler = /** @class */ (function (_super) { tslib.__extends(RxTestScheduler, _super); function RxTestScheduler() { return _super !== null && _super.apply(this, arguments) || this; } RxTestScheduler.prototype.run = function (callback) { var _this = this; try { var ret = _super.prototype.run.call(this, function (helpers) { var animator = _this._createAnimator(); var delegates = _this._createDelegates(); animationFrameProvider.delegate = animator.delegate; intervalProvider.delegate = delegates.interval; immediateProvider.delegate = delegates.immediate; dateTimestampProvider.delegate = _this; var origAnimate = helpers.animate; helpers.animate = function (marbles) { animator.animate(marbles); origAnimate(marbles); }; return callback(helpers); }); return ret; } finally { animationFrameProvider.delegate = undefined; intervalProvider.delegate = undefined; immediateProvider.delegate = undefined; dateTimestampProvider.delegate = undefined; } }; RxTestScheduler.prototype._createAnimator = function () { var _this = this; if (!this.runMode) { throw new Error('animate() must only be used in run mode'); } // The TestScheduler assigns a delegate to the provider that's used for // requestAnimationFrame (rAF). The delegate works in conjunction with the // animate run helper to coordinate the invocation of any rAF callbacks, // that are effected within tests, with the animation frames specified by // the test's author - in the marbles that are passed to the animate run // helper. This allows the test's author to write deterministic tests and // gives the author full control over when - or if - animation frames are // 'painted'. var lastHandle = 0; var map; var delegate = { requestAnimationFrame: function (callback) { if (!map) { throw new Error('animate() was not called within run()'); } var handle = ++lastHandle; map.set(handle, callback); return handle; }, cancelAnimationFrame: function (handle) { if (!map) { throw new Error('animate() was not called within run()'); } map.delete(handle); }, }; var animate = function (marbles) { if (map) { throw new Error('animate() must not be called more than once within run()'); } if (/[|#]/.test(marbles)) { throw new Error('animate() must not complete or error'); } map = new Map(); var messages = testing.TestScheduler.parseMarbles(marbles, undefined, undefined, undefined, true); for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { var message = messages_1[_i]; _this.schedule(function () { var now = _this.now(); // Capture the callbacks within the queue and clear the queue // before enumerating the callbacks, as callbacks might // reschedule themselves. (And, yeah, we're using a Map to represent // the queue, but the values are guaranteed to be returned in // insertion order, so it's all good. Trust me, I've read the docs.) var callbacks = Array.from(map.values()); map.clear(); for (var _i = 0, callbacks_1 = callbacks; _i < callbacks_1.length; _i++) { var callback = callbacks_1[_i]; callback(now); } }, message.frame); } }; return { animate: animate, delegate: delegate }; }; RxTestScheduler.prototype._create