feat(zone): add "on event done" zone hook
This commit is contained in:
		
							parent
							
								
									1eebceab27
								
							
						
					
					
						commit
						0e28297e68
					
				| @ -146,7 +146,7 @@ function _createNgZone(givenReporter: Function): NgZone { | ||||
|   var reporter = isPresent(givenReporter) ? givenReporter : defaultErrorReporter; | ||||
| 
 | ||||
|   var zone = new NgZone({enableLongStackTrace: assertionsEnabled()}); | ||||
|   zone.initCallbacks({onErrorHandler: reporter}); | ||||
|   zone.overrideOnErrorHandler(reporter); | ||||
|   return zone; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -57,7 +57,8 @@ export class LifeCycle { | ||||
|       this._changeDetector = changeDetector; | ||||
|     } | ||||
| 
 | ||||
|     zone.initCallbacks({onErrorHandler: this._errorHandler, onTurnDone: () => this.tick()}); | ||||
|     zone.overrideOnErrorHandler(this._errorHandler); | ||||
|     zone.overrideOnTurnDone(() => this.tick()); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | ||||
| @ -3,6 +3,9 @@ library angular.zone; | ||||
| import 'dart:async'; | ||||
| import 'package:stack_trace/stack_trace.dart' show Chain; | ||||
| 
 | ||||
| typedef void ZeroArgFunction(); | ||||
| typedef void ErrorHandlingFn(error, 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. | ||||
| @ -19,9 +22,10 @@ import 'package:stack_trace/stack_trace.dart' show Chain; | ||||
|  * instantiated. The default `onTurnDone` runs the Angular change detection. | ||||
|  */ | ||||
| class NgZone { | ||||
|   Function _onTurnStart; | ||||
|   Function _onTurnDone; | ||||
|   Function _onErrorHandler; | ||||
|   ZeroArgFunction _onTurnStart; | ||||
|   ZeroArgFunction _onTurnDone; | ||||
|   ZeroArgFunction _onEventDone; | ||||
|   ErrorHandlingFn _onErrorHandler; | ||||
| 
 | ||||
|   // Code executed in _mountZone does not trigger the onTurnDone. | ||||
|   Zone _mountZone; | ||||
| @ -65,21 +69,40 @@ class NgZone { | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Initializes the zone hooks. | ||||
|    * | ||||
|    * The given error handler should re-throw the passed exception. Otherwise, exceptions will not | ||||
|    * propagate outside of the [NgZone] and can alter the application execution flow. | ||||
|    * Not re-throwing could be used to help testing the code or advanced use cases. | ||||
|    * | ||||
|    * @param {Function} onTurnStart called before code executes in the inner zone for each VM turn | ||||
|    * @param {Function} onTurnDone called at the end of a VM turn if code has executed in the inner zone | ||||
|    * @param {Function} onErrorHandler called when an exception is thrown by a macro or micro task | ||||
|    * Sets the zone hook that is called just before Angular event turn starts. | ||||
|    * It is called once per browser event. | ||||
|    */ | ||||
|   void initCallbacks( | ||||
|       {Function onTurnStart, Function onTurnDone, Function onErrorHandler}) { | ||||
|     _onTurnStart = onTurnStart; | ||||
|     _onTurnDone = onTurnDone; | ||||
|     _onErrorHandler = onErrorHandler; | ||||
|   void overrideOnTurnStart(ZeroArgFunction onTurnStartFn) { | ||||
|     this._onTurnStart = onTurnStartFn; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the zone hook that is called immediately after Angular processes | ||||
|    * all pending microtasks. | ||||
|    */ | ||||
|   void overrideOnTurnDone(ZeroArgFunction onTurnDoneFn) { | ||||
|     this._onTurnDone = onTurnDoneFn; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the zone hook that is called immediately after the last turn in the | ||||
|    * current event completes. At this point Angular will no longer attempt to | ||||
|    * sync the UI. Any changes to the data model will not be reflected in the | ||||
|    * DOM. {@link onEventDoneFn} is executed outside Angular zone. | ||||
|    * | ||||
|    * This hook is useful for validating application state (e.g. in a test). | ||||
|    */ | ||||
|   void overrideOnEventDone(ZeroArgFunction onEventDoneFn) { | ||||
|     this._onEventDone = onEventDoneFn; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 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. | ||||
|    */ | ||||
|   void overrideOnErrorHandler(ErrorHandlingFn errorHandlingFn) { | ||||
|     this._onErrorHandler = errorHandlingFn; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
| @ -151,6 +174,10 @@ class NgZone { | ||||
|           try { | ||||
|             _inVmTurnDone = true; | ||||
|             parent.run(_innerZone, _onTurnDone);   | ||||
| 
 | ||||
|             if (_pendingMicrotasks == 0 && _onEventDone != null) { | ||||
|               runOutsideAngular(_onEventDone); | ||||
|             } | ||||
|           } finally { | ||||
|             _inVmTurnDone = false; | ||||
|             _hasExecutedCodeInInnerZone = false; | ||||
|  | ||||
| @ -23,6 +23,7 @@ export class NgZone { | ||||
| 
 | ||||
|   _onTurnStart: () => void; | ||||
|   _onTurnDone: () => void; | ||||
|   _onEventDone: () => void; | ||||
|   _onErrorHandler: (error, stack) => void; | ||||
| 
 | ||||
|   // Number of microtasks pending from _innerZone (& descendants)
 | ||||
| @ -53,6 +54,7 @@ export class NgZone { | ||||
|   constructor({enableLongStackTrace}) { | ||||
|     this._onTurnStart = null; | ||||
|     this._onTurnDone = null; | ||||
|     this._onEventDone = null; | ||||
|     this._onErrorHandler = null; | ||||
| 
 | ||||
|     this._pendingMicrotasks = 0; | ||||
| @ -70,22 +72,40 @@ export class NgZone { | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Initializes the zone hooks. | ||||
|    * | ||||
|    * @param {() => void} onTurnStart called before code executes in the inner zone for each VM turn | ||||
|    * @param {() => void} onTurnDone called at the end of a VM turn if code has executed in the inner | ||||
|    * zone | ||||
|    * @param {(error, stack) => void} onErrorHandler called when an exception is thrown by a macro or | ||||
|    * micro task | ||||
|    * Sets the zone hook that is called just before Angular event turn starts. | ||||
|    * It is called once per browser event. | ||||
|    */ | ||||
|   initCallbacks({onTurnStart, onTurnDone, onErrorHandler}: { | ||||
|     onTurnStart?: /*() => void*/ Function, | ||||
|     onTurnDone?: /*() => void*/ Function, | ||||
|     onErrorHandler?: /*(error, stack) => void*/ Function | ||||
|   } = {}) { | ||||
|     this._onTurnStart = normalizeBlank(onTurnStart); | ||||
|     this._onTurnDone = normalizeBlank(onTurnDone); | ||||
|     this._onErrorHandler = normalizeBlank(onErrorHandler); | ||||
|   overrideOnTurnStart(onTurnStartFn: Function): void { | ||||
|     this._onTurnStart = normalizeBlank(onTurnStartFn); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the zone hook that is called immediately after Angular processes | ||||
|    * all pending microtasks. | ||||
|    */ | ||||
|   overrideOnTurnDone(onTurnDoneFn: Function): void { | ||||
|     this._onTurnDone = normalizeBlank(onTurnDoneFn); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the zone hook that is called immediately after the last turn in the | ||||
|    * current event completes. At this point Angular will no longer attempt to | ||||
|    * sync the UI. Any changes to the data model will not be reflected in the | ||||
|    * DOM. {@link onEventDoneFn} is executed outside Angular zone. | ||||
|    * | ||||
|    * This hook is useful for validating application state (e.g. in a test). | ||||
|    */ | ||||
|   overrideOnEventDone(onEventDoneFn: Function): void { | ||||
|     this._onEventDone = normalizeBlank(onEventDoneFn); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 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. | ||||
|    */ | ||||
|   overrideOnErrorHandler(errorHandlingFn: Function): void { | ||||
|     this._onErrorHandler = normalizeBlank(errorHandlingFn); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
| @ -172,6 +192,9 @@ export class NgZone { | ||||
|                     try { | ||||
|                       this._inVmTurnDone = true; | ||||
|                       parentRun.call(ngZone._innerZone, ngZone._onTurnDone); | ||||
|                       if (ngZone._pendingMicrotasks === 0 && isPresent(ngZone._onEventDone)) { | ||||
|                         ngZone.runOutsideAngular(ngZone._onEventDone); | ||||
|                       } | ||||
|                     } finally { | ||||
|                       this._inVmTurnDone = false; | ||||
|                       ngZone._hasExecutedCodeInInnerZone = false; | ||||
|  | ||||
| @ -46,7 +46,8 @@ export function main() { | ||||
| 
 | ||||
|     function createZone(enableLongStackTrace) { | ||||
|       var zone = new NgZone({enableLongStackTrace: enableLongStackTrace}); | ||||
|       zone.initCallbacks({onTurnStart: _log.fn('onTurnStart'), onTurnDone: _log.fn('onTurnDone')}); | ||||
|       zone.overrideOnTurnStart(_log.fn('onTurnStart')); | ||||
|       zone.overrideOnTurnDone(_log.fn('onTurnDone')); | ||||
|       return zone; | ||||
|     } | ||||
| 
 | ||||
| @ -63,7 +64,7 @@ export function main() { | ||||
| 
 | ||||
|       it('should produce long stack traces', inject([AsyncTestCompleter], (async) => { | ||||
|            macroTask(() => { | ||||
|              _zone.initCallbacks({onErrorHandler: logError}); | ||||
|              _zone.overrideOnErrorHandler(logError); | ||||
|              var c = PromiseWrapper.completer(); | ||||
| 
 | ||||
|              _zone.run(() => { | ||||
| @ -86,7 +87,7 @@ export function main() { | ||||
|       it('should produce long stack traces (when using microtasks)', | ||||
|          inject([AsyncTestCompleter], (async) => { | ||||
|            macroTask(() => { | ||||
|              _zone.initCallbacks({onErrorHandler: logError}); | ||||
|              _zone.overrideOnErrorHandler(logError); | ||||
|              var c = PromiseWrapper.completer(); | ||||
| 
 | ||||
|              _zone.run(() => { | ||||
| @ -114,7 +115,7 @@ export function main() { | ||||
| 
 | ||||
|       it('should disable long stack traces', inject([AsyncTestCompleter], (async) => { | ||||
|            macroTask(() => { | ||||
|              _zone.initCallbacks({onErrorHandler: logError}); | ||||
|              _zone.overrideOnErrorHandler(logError); | ||||
|              var c = PromiseWrapper.completer(); | ||||
| 
 | ||||
|              _zone.run(() => { | ||||
| @ -160,6 +161,77 @@ function commonTests() { | ||||
|          }); | ||||
|        })); | ||||
| 
 | ||||
|     it('should call onEventDone once at the end of event', inject([AsyncTestCompleter], (async) => { | ||||
|          // The test is set up in a way that causes the zone loop to run onTurnDone twice
 | ||||
|          // then verified that onEventDone is only called once at the end
 | ||||
|          _zone.overrideOnTurnStart(null); | ||||
|          _zone.overrideOnEventDone(() => { _log.add('onEventDone'); }); | ||||
| 
 | ||||
|          var times = 0; | ||||
|          _zone.overrideOnTurnDone(() => { | ||||
|            times++; | ||||
|            _log.add(`onTurnDone ${times}`); | ||||
|            if (times < 2) { | ||||
|              // Scheduling a microtask causes a second digest
 | ||||
|              microTask(() => {}); | ||||
|            } | ||||
|          }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(_log.fn('run')); }); | ||||
| 
 | ||||
|          macroTask(() => { | ||||
|            expect(_log.result()).toEqual('run; onTurnDone 1; onTurnDone 2; onEventDone'); | ||||
|            async.done(); | ||||
|          }); | ||||
|        })); | ||||
| 
 | ||||
|     it('should not allow onEventDone to cause further digests', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          _zone.overrideOnTurnStart(null); | ||||
| 
 | ||||
|          var eventDone = false; | ||||
|          _zone.overrideOnEventDone(() => { | ||||
|            if (eventDone) throw 'Should not call this more than once'; | ||||
|            _log.add('onEventDone'); | ||||
|            // If not implemented correctly, this microtask will cause another digest,
 | ||||
|            // which is not what we want.
 | ||||
|            microTask(() => {}); | ||||
|            eventDone = true; | ||||
|          }); | ||||
| 
 | ||||
|          _zone.overrideOnTurnDone(() => { _log.add('onTurnDone'); }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(_log.fn('run')); }); | ||||
| 
 | ||||
|          macroTask(() => { | ||||
|            expect(_log.result()).toEqual('run; onTurnDone; onEventDone'); | ||||
|            async.done(); | ||||
|          }); | ||||
|        })); | ||||
| 
 | ||||
|     it('should run async tasks scheduled inside onEventDone outside Angular zone', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          _zone.overrideOnTurnStart(null); | ||||
| 
 | ||||
|          _zone.overrideOnEventDone(() => { | ||||
|            _log.add('onEventDone'); | ||||
|            // If not implemented correctly, this time will cause another digest,
 | ||||
|            // which is not what we want.
 | ||||
|            TimerWrapper.setTimeout(() => { _log.add('asyncTask'); }, 5); | ||||
|          }); | ||||
| 
 | ||||
|          _zone.overrideOnTurnDone(() => { _log.add('onTurnDone'); }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(_log.fn('run')); }); | ||||
| 
 | ||||
|          macroTask(() => { | ||||
|            TimerWrapper.setTimeout(() => { | ||||
|              expect(_log.result()).toEqual('run; onTurnDone; onEventDone; asyncTask'); | ||||
|              async.done(); | ||||
|            }, 10); | ||||
|          }); | ||||
|        })); | ||||
| 
 | ||||
|     it('should call onTurnStart once before a turn and onTurnDone once after the turn', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
| 
 | ||||
| @ -201,12 +273,11 @@ function commonTests() { | ||||
| 
 | ||||
|     it('should not run onTurnStart and onTurnDone for nested Zone.run invoked from onTurnDone', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          _zone.initCallbacks({ | ||||
|            onTurnDone: () => { | ||||
|          _zone.overrideOnTurnStart(null); | ||||
|          _zone.overrideOnTurnDone(() => { | ||||
|            _log.add('onTurnDone:started'); | ||||
|            _zone.run(() => _log.add('nested run')); | ||||
|            _log.add('onTurnDone:finished'); | ||||
|            } | ||||
|          }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(() => { _log.add('start run'); }); }); | ||||
| @ -306,9 +377,8 @@ function commonTests() { | ||||
|            'onTurnDone after executing the task', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          var ran = false; | ||||
|          _zone.initCallbacks({ | ||||
|            onTurnStart: _log.fn('onTurnStart'), | ||||
|            onTurnDone: () => { | ||||
|          _zone.overrideOnTurnStart(_log.fn('onTurnStart')); | ||||
|          _zone.overrideOnTurnDone(() => { | ||||
|            _log.add('onTurnDone(begin)'); | ||||
|            if (!ran) { | ||||
|              microTask(() => { | ||||
| @ -318,7 +388,6 @@ function commonTests() { | ||||
|            } | ||||
| 
 | ||||
|            _log.add('onTurnDone(end)'); | ||||
|            } | ||||
|          }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(_log.fn('run')); }); | ||||
| @ -338,9 +407,8 @@ function commonTests() { | ||||
|            'a scheduleMicrotask in run', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          var ran = false; | ||||
|          _zone.initCallbacks({ | ||||
|            onTurnStart: _log.fn('onTurnStart'), | ||||
|            onTurnDone: () => { | ||||
|          _zone.overrideOnTurnStart(_log.fn('onTurnStart')); | ||||
|          _zone.overrideOnTurnDone(() => { | ||||
|            _log.add('onTurnDone(begin)'); | ||||
|            if (!ran) { | ||||
|              _log.add('onTurnDone(scheduleMicrotask)'); | ||||
| @ -350,7 +418,6 @@ function commonTests() { | ||||
|              }); | ||||
|            } | ||||
|            _log.add('onTurnDone(end)'); | ||||
|            } | ||||
|          }); | ||||
| 
 | ||||
|          macroTask(() => { | ||||
| @ -376,8 +443,7 @@ function commonTests() { | ||||
|          var donePromiseRan = false; | ||||
|          var startPromiseRan = false; | ||||
| 
 | ||||
|          _zone.initCallbacks({ | ||||
|            onTurnStart: () => { | ||||
|          _zone.overrideOnTurnStart(() => { | ||||
|            _log.add('onTurnStart(begin)'); | ||||
|            if (!startPromiseRan) { | ||||
|              _log.add('onTurnStart(schedulePromise)'); | ||||
| @ -385,8 +451,8 @@ function commonTests() { | ||||
|              startPromiseRan = true; | ||||
|            } | ||||
|            _log.add('onTurnStart(end)'); | ||||
|            }, | ||||
|            onTurnDone: () => { | ||||
|          }); | ||||
|          _zone.overrideOnTurnDone(() => { | ||||
|            _log.add('onTurnDone(begin)'); | ||||
|            if (!donePromiseRan) { | ||||
|              _log.add('onTurnDone(schedulePromise)'); | ||||
| @ -394,7 +460,6 @@ function commonTests() { | ||||
|              donePromiseRan = true; | ||||
|            } | ||||
|            _log.add('onTurnDone(end)'); | ||||
|            } | ||||
|          }); | ||||
| 
 | ||||
|          macroTask(() => { | ||||
| @ -507,7 +572,7 @@ function commonTests() { | ||||
|     it('should call the on error callback when it is defined', | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          macroTask(() => { | ||||
|            _zone.initCallbacks({onErrorHandler: logError}); | ||||
|            _zone.overrideOnErrorHandler(logError); | ||||
| 
 | ||||
|            var exception = new BaseException('sync'); | ||||
| 
 | ||||
| @ -520,7 +585,7 @@ function commonTests() { | ||||
|        })); | ||||
| 
 | ||||
|     it('should call onError for errors from microtasks', inject([AsyncTestCompleter], (async) => { | ||||
|          _zone.initCallbacks({onErrorHandler: logError}); | ||||
|          _zone.overrideOnErrorHandler(logError); | ||||
| 
 | ||||
|          var exception = new BaseException('async'); | ||||
| 
 | ||||
| @ -537,7 +602,8 @@ function commonTests() { | ||||
|        inject([AsyncTestCompleter], (async) => { | ||||
|          var exception = new BaseException('fromOnTurnDone'); | ||||
| 
 | ||||
|          _zone.initCallbacks({onErrorHandler: logError, onTurnDone: () => { throw exception; }}); | ||||
|          _zone.overrideOnErrorHandler(logError); | ||||
|          _zone.overrideOnTurnDone(() => { throw exception; }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(() => {}); }); | ||||
| 
 | ||||
| @ -554,7 +620,8 @@ function commonTests() { | ||||
| 
 | ||||
|          var exception = new BaseException('fromOnTurnDone'); | ||||
| 
 | ||||
|          _zone.initCallbacks({onErrorHandler: logError, onTurnDone: () => { throw exception; }}); | ||||
|          _zone.overrideOnErrorHandler(logError); | ||||
|          _zone.overrideOnTurnDone(() => { throw exception; }); | ||||
| 
 | ||||
|          macroTask(() => { _zone.run(() => { microTask(() => { asyncRan = true; }); }); }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user