feat: move NgZone to Stream/Observable-based callback API
BREAKING CHANGES: - deprecates these methods in NgZone: overrideOnTurnStart, overrideOnTurnDone, overrideOnEventDone, overrideOnErrorHandler - introduces new API in NgZone that may shadow other API used by existing applications.
This commit is contained in:
@ -35,6 +35,17 @@ class WrappedTimer implements Timer {
|
||||
bool get isActive => _timer.isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores error information; delivered via [NgZone.onError] stream.
|
||||
*/
|
||||
class NgZoneError {
|
||||
/// Error object thrown.
|
||||
final error;
|
||||
/// Either long or short chain of stack traces.
|
||||
final List stackTrace;
|
||||
NgZoneError(this.error, this.stackTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Zone` wrapper that lets you schedule tasks after its private microtask queue is exhausted but
|
||||
* before the next "VM turn", i.e. event loop iteration.
|
||||
@ -56,6 +67,12 @@ class NgZone {
|
||||
ZeroArgFunction _onEventDone;
|
||||
ErrorHandlingFn _onErrorHandler;
|
||||
|
||||
final _onTurnStartCtrl = new StreamController.broadcast(sync: true);
|
||||
final _onTurnDoneCtrl = new StreamController.broadcast(sync: true);
|
||||
final _onEventDoneCtrl = new StreamController.broadcast(sync: true);
|
||||
final _onErrorCtrl =
|
||||
new StreamController<NgZoneError>.broadcast(sync: true);
|
||||
|
||||
// Code executed in _mountZone does not trigger the onTurnDone.
|
||||
Zone _mountZone;
|
||||
// _innerZone is the child of _mountZone. Any code executed in this zone will trigger the
|
||||
@ -103,18 +120,43 @@ class NgZone {
|
||||
* Sets the zone hook that is called just before Angular event turn starts.
|
||||
* It is called once per browser event.
|
||||
*/
|
||||
@Deprecated('Use onTurnStart Stream instead')
|
||||
void overrideOnTurnStart(ZeroArgFunction onTurnStartFn) {
|
||||
_onTurnStart = onTurnStartFn;
|
||||
}
|
||||
|
||||
void _notifyOnTurnStart() {
|
||||
this._onTurnStartCtrl.add(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers just before Angular event turn starts.
|
||||
*
|
||||
* Emits an event once per browser task that is handled by Angular.
|
||||
*/
|
||||
Stream get onTurnStart => _onTurnStartCtrl.stream;
|
||||
|
||||
/**
|
||||
* Sets the zone hook that is called immediately after Angular processes
|
||||
* all pending microtasks.
|
||||
*/
|
||||
@Deprecated('Use onTurnDone Stream instead')
|
||||
void overrideOnTurnDone(ZeroArgFunction onTurnDoneFn) {
|
||||
_onTurnDone = onTurnDoneFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers immediately after the Angular zone is done processing
|
||||
* the current turn and any microtasks scheduled from that turn.
|
||||
*
|
||||
* Used by Angular as a signal to kick off change-detection.
|
||||
*/
|
||||
Stream get onTurnDone => _onTurnDoneCtrl.stream;
|
||||
|
||||
void _notifyOnTurnDone() {
|
||||
this._onTurnDoneCtrl.add(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the zone hook that is called immediately after the last turn in
|
||||
* an event completes. At this point Angular will no longer attempt to
|
||||
@ -123,6 +165,7 @@ class NgZone {
|
||||
*
|
||||
* This hook is useful for validating application state (e.g. in a test).
|
||||
*/
|
||||
@Deprecated('Use onEventDone Stream instead')
|
||||
void overrideOnEventDone(ZeroArgFunction onEventDoneFn,
|
||||
[bool waitForAsync = false]) {
|
||||
_onEventDone = onEventDoneFn;
|
||||
@ -136,15 +179,55 @@ class NgZone {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers immediately after the final `onTurnDone` callback
|
||||
* before ending VM event.
|
||||
*
|
||||
* This event is useful for validating application state (e.g. in a test).
|
||||
*/
|
||||
Stream get onEventDone => _onEventDoneCtrl.stream;
|
||||
|
||||
void _notifyOnEventDone() {
|
||||
this._onEventDoneCtrl.add(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding microtasks.
|
||||
*/
|
||||
bool get hasPendingMicrotasks => _pendingMicrotasks > 0;
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding timers.
|
||||
*/
|
||||
bool get hasPendingTimers => _pendingTimers.isNotEmpty;
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding asychnronous tasks of any kind that are
|
||||
* scheduled to run within Angular zone.
|
||||
*
|
||||
* Useful as a signal of UI stability. For example, when a test reaches a
|
||||
* point when [hasPendingAsyncTasks] is `false` it might be a good time to run
|
||||
* test expectations.
|
||||
*/
|
||||
bool get hasPendingAsyncTasks => hasPendingMicrotasks || hasPendingTimers;
|
||||
|
||||
/**
|
||||
* Sets the zone hook that is called when an error is uncaught in the
|
||||
* Angular zone. The first argument is the error. The second argument is
|
||||
* the stack trace.
|
||||
*/
|
||||
@Deprecated('Use onError Stream instead')
|
||||
void overrideOnErrorHandler(ErrorHandlingFn errorHandlingFn) {
|
||||
_onErrorHandler = errorHandlingFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers whenever an error happens within the zone.
|
||||
*
|
||||
* Useful for logging.
|
||||
*/
|
||||
Stream get onError => _onErrorCtrl.stream;
|
||||
|
||||
/**
|
||||
* Runs `fn` in the inner zone and returns whatever it returns.
|
||||
*
|
||||
@ -194,6 +277,7 @@ class NgZone {
|
||||
void _maybeStartVmTurn(ZoneDelegate parent) {
|
||||
if (!_hasExecutedCodeInInnerZone) {
|
||||
_hasExecutedCodeInInnerZone = true;
|
||||
parent.run(_innerZone, _notifyOnTurnStart);
|
||||
if (_onTurnStart != null) {
|
||||
parent.run(_innerZone, _onTurnStart);
|
||||
}
|
||||
@ -209,19 +293,25 @@ class NgZone {
|
||||
_nestedRun--;
|
||||
// If there are no more pending microtasks and we are not in a recursive call, this is the end of a turn
|
||||
if (_pendingMicrotasks == 0 && _nestedRun == 0 && !_inVmTurnDone) {
|
||||
if (_onTurnDone != null && _hasExecutedCodeInInnerZone) {
|
||||
if (_hasExecutedCodeInInnerZone) {
|
||||
// Trigger onTurnDone at the end of a turn if _innerZone has executed some code
|
||||
try {
|
||||
_inVmTurnDone = true;
|
||||
parent.run(_innerZone, _onTurnDone);
|
||||
_notifyOnTurnDone();
|
||||
if (_onTurnDone != null) {
|
||||
parent.run(_innerZone, _onTurnDone);
|
||||
}
|
||||
} finally {
|
||||
_inVmTurnDone = false;
|
||||
_hasExecutedCodeInInnerZone = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingMicrotasks == 0 && _onEventDone != null) {
|
||||
runOutsideAngular(_onEventDone);
|
||||
if (_pendingMicrotasks == 0) {
|
||||
_notifyOnEventDone();
|
||||
if (_onEventDone != null) {
|
||||
runOutsideAngular(_onEventDone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,9 +338,14 @@ class NgZone {
|
||||
|
||||
// Called by Chain.capture() on errors when long stack traces are enabled
|
||||
void _onErrorWithLongStackTrace(error, Chain chain) {
|
||||
if (_onErrorHandler != null) {
|
||||
if (_onErrorHandler != null || _onErrorCtrl.hasListener) {
|
||||
final traces = chain.terse.traces.map((t) => t.toString()).toList();
|
||||
_onErrorHandler(error, traces);
|
||||
if (_onErrorCtrl.hasListener) {
|
||||
_onErrorCtrl.add(new NgZoneError(error, traces));
|
||||
}
|
||||
if (_onErrorHandler != null) {
|
||||
_onErrorHandler(error, traces);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@ -258,8 +353,13 @@ class NgZone {
|
||||
|
||||
// Outer zone handleUnchaughtError when long stack traces are not used
|
||||
void _onErrorWithoutLongStackTrace(error, StackTrace trace) {
|
||||
if (_onErrorHandler != null) {
|
||||
_onErrorHandler(error, [trace.toString()]);
|
||||
if (_onErrorHandler != null || _onErrorCtrl.hasListener) {
|
||||
if (_onErrorHandler != null) {
|
||||
_onErrorHandler(error, [trace.toString()]);
|
||||
}
|
||||
if (_onErrorCtrl.hasListener) {
|
||||
_onErrorCtrl.add(new NgZoneError(error, [trace.toString()]));
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
@ -1,19 +1,22 @@
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {normalizeBlank, isPresent, global} from 'angular2/src/core/facade/lang';
|
||||
import {ObservableWrapper, EventEmitter} from 'angular2/src/core/facade/async';
|
||||
import {wtfLeave, wtfCreateScope, WtfScopeFn} from '../profile/profile';
|
||||
|
||||
|
||||
export interface NgZoneZone extends Zone {
|
||||
/** @internal */
|
||||
_innerZone: boolean;
|
||||
}
|
||||
|
||||
export interface ZeroArgFunction {
|
||||
(): void;
|
||||
}
|
||||
export interface ZeroArgFunction { (): void; }
|
||||
|
||||
export interface ErrorHandlingFn {
|
||||
(error: any, stackTrace: any): void;
|
||||
export interface ErrorHandlingFn { (error: any, stackTrace: any): void; }
|
||||
|
||||
/**
|
||||
* Stores error information; delivered via [NgZone.onError] stream.
|
||||
*/
|
||||
export class NgZoneError {
|
||||
constructor(public error: any, public stackTrace: any) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,6 +112,15 @@ export class NgZone {
|
||||
/** @internal */
|
||||
_onErrorHandler: ErrorHandlingFn;
|
||||
|
||||
/** @internal */
|
||||
_onTurnStartEvents: EventEmitter;
|
||||
/** @internal */
|
||||
_onTurnDoneEvents: EventEmitter;
|
||||
/** @internal */
|
||||
_onEventDoneEvents: EventEmitter;
|
||||
/** @internal */
|
||||
_onErrorEvents: EventEmitter;
|
||||
|
||||
// Number of microtasks pending from _innerZone (& descendants)
|
||||
/** @internal */
|
||||
_pendingMicrotasks: number = 0;
|
||||
@ -146,6 +158,10 @@ export class NgZone {
|
||||
this._disabled = true;
|
||||
this._mountZone = null;
|
||||
}
|
||||
this._onTurnStartEvents = new EventEmitter(false);
|
||||
this._onTurnDoneEvents = new EventEmitter(false);
|
||||
this._onEventDoneEvents = new EventEmitter(false);
|
||||
this._onErrorEvents = new EventEmitter(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,11 +171,24 @@ export class NgZone {
|
||||
* The hook is called once per browser task that is handled by Angular.
|
||||
*
|
||||
* Setting the hook overrides any previously set hook.
|
||||
*
|
||||
* @deprecated this API will be removed in the future. Use `onTurnStart` instead.
|
||||
*/
|
||||
overrideOnTurnStart(onTurnStartHook: ZeroArgFunction): void {
|
||||
this._onTurnStart = normalizeBlank(onTurnStartHook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers just before Angular event turn starts.
|
||||
*
|
||||
* Emits an event once per browser task that is handled by Angular.
|
||||
*/
|
||||
get onTurnStart(): /* Subject */ any { return this._onTurnStartEvents; }
|
||||
|
||||
_notifyOnTurnStart(parentRun): void {
|
||||
parentRun.call(this._innerZone, () => { this._onTurnStartEvents.next(null); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the zone hook that is called immediately after Angular zone is done processing the current
|
||||
* task and any microtasks scheduled from that task.
|
||||
@ -169,11 +198,25 @@ export class NgZone {
|
||||
* The hook is called once per browser task that is handled by Angular.
|
||||
*
|
||||
* Setting the hook overrides any previously set hook.
|
||||
*
|
||||
* @deprecated this API will be removed in the future. Use `onTurnDone` instead.
|
||||
*/
|
||||
overrideOnTurnDone(onTurnDoneHook: ZeroArgFunction): void {
|
||||
this._onTurnDone = normalizeBlank(onTurnDoneHook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers immediately after Angular zone is done processing
|
||||
* the current turn and any microtasks scheduled from that turn.
|
||||
*
|
||||
* Used by Angular as a signal to kick off change-detection.
|
||||
*/
|
||||
get onTurnDone() { return this._onTurnDoneEvents; }
|
||||
|
||||
_notifyOnTurnDone(parentRun): void {
|
||||
parentRun.call(this._innerZone, () => { this._onTurnDoneEvents.next(null); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the zone hook that is called immediately after the `onTurnDone` callback is called and any
|
||||
* microstasks scheduled from within that callback are drained.
|
||||
@ -184,6 +227,8 @@ export class NgZone {
|
||||
* This hook is useful for validating application state (e.g. in a test).
|
||||
*
|
||||
* Setting the hook overrides any previously set hook.
|
||||
*
|
||||
* @deprecated this API will be removed in the future. Use `onEventDone` instead.
|
||||
*/
|
||||
overrideOnEventDone(onEventDoneFn: ZeroArgFunction, opt_waitForAsync: boolean = false): void {
|
||||
var normalizedOnEventDone = normalizeBlank(onEventDoneFn);
|
||||
@ -198,15 +243,51 @@ export class NgZone {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers immediately after the final `onTurnDone` callback
|
||||
* before ending VM event.
|
||||
*
|
||||
* This event is useful for validating application state (e.g. in a test).
|
||||
*/
|
||||
get onEventDone() { return this._onEventDoneEvents; }
|
||||
|
||||
_notifyOnEventDone(): void {
|
||||
this.runOutsideAngular(() => { this._onEventDoneEvents.next(null); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding microtasks.
|
||||
*/
|
||||
get hasPendingMicrotasks(): boolean { return this._pendingMicrotasks > 0; }
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding timers.
|
||||
*/
|
||||
get hasPendingTimers(): boolean { return this._pendingTimeouts.length > 0; }
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding asychnronous tasks of any kind that are
|
||||
* scheduled to run within Angular zone.
|
||||
*
|
||||
* Useful as a signal of UI stability. For example, when a test reaches a
|
||||
* point when [hasPendingAsyncTasks] is `false` it might be a good time to run
|
||||
* test expectations.
|
||||
*/
|
||||
get hasPendingAsyncTasks(): boolean { return this.hasPendingMicrotasks || this.hasPendingTimers; }
|
||||
|
||||
/**
|
||||
* Sets the zone hook that is called when an error is thrown in the Angular zone.
|
||||
*
|
||||
* Setting the hook overrides any previously set hook.
|
||||
*
|
||||
* @deprecated this API will be removed in the future. Use `onError` instead.
|
||||
*/
|
||||
overrideOnErrorHandler(errorHandler: ErrorHandlingFn) {
|
||||
this._onErrorHandler = normalizeBlank(errorHandler);
|
||||
}
|
||||
|
||||
get onError() { return this._onErrorEvents; }
|
||||
|
||||
/**
|
||||
* Executes the `fn` function synchronously within the Angular zone and returns value returned by
|
||||
* the function.
|
||||
@ -257,10 +338,10 @@ export class NgZone {
|
||||
var errorHandling;
|
||||
|
||||
if (enableLongStackTrace) {
|
||||
errorHandling = StringMapWrapper.merge(Zone.longStackTraceZone,
|
||||
{onError: function(e) { ngZone._onError(this, e); }});
|
||||
errorHandling = StringMapWrapper.merge(
|
||||
Zone.longStackTraceZone, {onError: function(e) { ngZone._notifyOnError(this, e); }});
|
||||
} else {
|
||||
errorHandling = {onError: function(e) { ngZone._onError(this, e); }};
|
||||
errorHandling = {onError: function(e) { ngZone._notifyOnError(this, e); }};
|
||||
}
|
||||
|
||||
return zone.fork(errorHandling)
|
||||
@ -271,6 +352,7 @@ export class NgZone {
|
||||
ngZone._nestedRun++;
|
||||
if (!ngZone._hasExecutedCodeInInnerZone) {
|
||||
ngZone._hasExecutedCodeInInnerZone = true;
|
||||
ngZone._notifyOnTurnStart(parentRun);
|
||||
if (ngZone._onTurnStart) {
|
||||
parentRun.call(ngZone._innerZone, ngZone._onTurnStart);
|
||||
}
|
||||
@ -285,18 +367,24 @@ export class NgZone {
|
||||
// to run()).
|
||||
if (ngZone._pendingMicrotasks == 0 && ngZone._nestedRun == 0 &&
|
||||
!this._inVmTurnDone) {
|
||||
if (ngZone._onTurnDone && ngZone._hasExecutedCodeInInnerZone) {
|
||||
if (ngZone._hasExecutedCodeInInnerZone) {
|
||||
try {
|
||||
this._inVmTurnDone = true;
|
||||
parentRun.call(ngZone._innerZone, ngZone._onTurnDone);
|
||||
ngZone._notifyOnTurnDone(parentRun);
|
||||
if (ngZone._onTurnDone) {
|
||||
parentRun.call(ngZone._innerZone, ngZone._onTurnDone);
|
||||
}
|
||||
} finally {
|
||||
this._inVmTurnDone = false;
|
||||
ngZone._hasExecutedCodeInInnerZone = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngZone._pendingMicrotasks === 0 && isPresent(ngZone._onEventDone)) {
|
||||
ngZone.runOutsideAngular(ngZone._onEventDone);
|
||||
if (ngZone._pendingMicrotasks === 0) {
|
||||
ngZone._notifyOnEventDone();
|
||||
if (isPresent(ngZone._onEventDone)) {
|
||||
ngZone.runOutsideAngular(ngZone._onEventDone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -340,17 +428,22 @@ export class NgZone {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_onError(zone, e): void {
|
||||
if (isPresent(this._onErrorHandler)) {
|
||||
_notifyOnError(zone, e): void {
|
||||
if (isPresent(this._onErrorHandler) || ObservableWrapper.hasSubscribers(this._onErrorEvents)) {
|
||||
var trace = [normalizeBlank(e.stack)];
|
||||
|
||||
while (zone && zone.constructedAtException) {
|
||||
trace.push(zone.constructedAtException.get());
|
||||
zone = zone.parent;
|
||||
}
|
||||
this._onErrorHandler(e, trace);
|
||||
if (ObservableWrapper.hasSubscribers(this._onErrorEvents)) {
|
||||
ObservableWrapper.callNext(this._onErrorEvents, new NgZoneError(e, trace));
|
||||
}
|
||||
if (isPresent(this._onErrorHandler)) {
|
||||
this._onErrorHandler(e, trace);
|
||||
}
|
||||
} else {
|
||||
console.log('## _onError ##');
|
||||
console.log('## _notifyOnError ##');
|
||||
console.log(e.stack);
|
||||
throw e;
|
||||
}
|
||||
|
Reference in New Issue
Block a user