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
JavaScript
(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` 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` 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