@JiaLiPassion
A Zone is an execution context that persists across async tasks.
Created by Brian Ford inspired by Dart.
Provide execution context
Provide async task lifecycle hook
Provide error handler for async operations
const testObj = {
testFunc: function() {
console.log('this in testFunc is:', this);
}
};
testObj.testFunc();
const newTestFunc = testObj.testFunc;
newTestFunc();
const newObj = {};
newTestFunc.apply(newObj);
const bindObj = {};
const boundFunc = testObj.testFunc.bind(bindObj);
boundFunc();
zone.run(function() {
// function is in the zone
// just like `this`, we have a zoneThis === zone
expect(zoneThis).toBe(zone);
setTimeout(function() {
// the callback of async operation
// will also have a zoneThis === zone
// which is the zoneContext when this async operation
// is scheduled.
expect(zoneThis).toBe(zone);
});
Promise.resolve(1).then(function() {
// all async opreations will be in the same zone
// when they are scheduled.
expect(zoneThis).toBe(zone);
});
});
zone.run(function() {
expect(Zone.current).toBe(zone);
setTimeout(function() {
expect(Zone.current).toBe(zone);
});
});
const zone = Zone.current.fork({
name: 'zone',
properties: {a: 1}
});
zone.run(function() {
expect(Zone.current.get('a')).toBe(1);
setTimeout(function() {
expect(Zone.current.get('a')).toBe(1);
});
});
setTimeout(function() {
console.log('callback invoked');
}, 1000);
var zone = Zone.current.fork({
name: 'hook',
onScheduleTask: ...,
onInvokeTask: ....,
onHasTask: ...
});
zone.run(() => {
setTimeout(() => {
console.log('timer callback invoked');
}, 100);
});
zone.fork(zoneSpec);
interface ZoneSpec {
name: string;
onScheduleTask: ...,
onInvokeTask: ...,
onCancelTask: ...,
onInvoke: ...,
...,
}
const stackTraceZone = Zone.current.fork({
name: 'stackTraceZone',
onScheduleTask: ...
onInvokeTask: ...
});
const logZone = stackTraceZone.fork({
name: 'logZone',
onInvokeTask: ...
});
logZone.run(() => {
setTimeout(...)
});
rootZone
stackTraceZone
logZone
onInvokeTask
onInvokeTask
const stackTraceZone = Zone.fork({
name: 'stackTraceZone',
onInvokeTask: (...) => {
console.log('stackTraceZone', Zone.current.name);
return delegate.invokeTask(...)
}
});
const logZone = Zone.fork({
name: 'logZone',
parentZone: stackTraceZone,
onInvokeTask: (...) => {
console.log('logZone', Zone.current.name);
return delegate.invokeTask(...)
}
});
logZone.run(() => {
setTimeout(...)
});
// logZone logZone
// stackTraceZone logZone
class Zone { constructor(public parent, public name) {}}
class StackTraceZone extends Zone {
constructor(parent) { super(parent, 'stackTraceZone');}
onInvokeTask() {
...
this.parent.onInvokeTask();
}
}
class LogZone extends Zone {
constructor(parent) {super(parent, 'logZone');}
onInvokeTask() {
...
this.parent.onInvokeTask();
}
}
const stackTraceZone = new StackTraceZone(rootZone);
const logZone = new LogZone(stackTraceZone);
// logZone logZone
// stackTraceZone stackTraceZone
Zone
ZoneDelegate
name: stackTraceZone
parent: rootZone
name: logZone
run()
parent: stackTraceZone
onInvoke
onInvoke(parentDelegate, ...)
const zoneA = Zone.current.fork({name: 'zoneA'});
const zoneB = Zone.current.fork({name: 'zoneB'});
zoneA.run(() => {
// now we are in zoneA
zoneB.run(() => {
// now we are in zoneB
});
// now we are back in zoneA
...
});
// currentZoneFrame = {parent: null, zone: rootZone}
zoneA.run(() => { // currentZoneFrame = {parent: currentZoneFrame, zone: zoneA}
// now we are in zoneA
zoneB.run(() => { // currentZoneFrame = {parent: currentZoneFrame, zone: zoneB}
// now we are in zoneB
}); // currentZoneFrame = currentZoneFrame.parent
// now we are back in zoneA
...
});
// implementation of Zone.current
get current() {
return currentZoneFrame.zone;
}
currentZoneFrame: ZoneFrame;
interface ZoneFrame {
parent: ZoneFrame;
zone: Zone;
}
Zone.current.fork(
{
name: 'error',
onHandleError: (delegate, curr, target, error) => {
logger.error(error);
return delegate.handleError(target, error);
}
}).runGuarded(() => {
setTimeout(() => {
throw new Error('timeout error');
});
setTimeout(() => {
throw new Error('another timeout error');
});
});
function testFunc() {
console.log('this in testFunc is:', this);
}
const bindTarget = {};
const boundFunc = testFunc.bind(bindTarget);
boundFunc.apply(newObj);
// ------------------------
//
function testZoneFunc() {
console.log('zoneThis in testFunc is:', Zone.current);
}
const zoneA = Zone.current.fork({name: 'zoneA'});
const wrappedTestZoneFunc = zoneA.wrap(testZoneFunc);
zoneB.run(wrappedTestZoneFunc);
zone.scheduleMicroTask('myOwnMicroTask', callbackFn);
// looks like
Promise.resolve().then(() => {
callbackFn();
});
// zone has it's own microTaskQueue
p.then(() => {});
p.then(() => {});
// without zone, there will be 2 microTasks in
// native microTask queue
// with zone, there will be only 1 microTask in
// native microTask queue
drainMicroTaskQueue() {
zoneMicroTasks.forEach(t => {
t.zone.runTask(t);
});
}
zone microTaskQueue
native
microTaskQueue
zoneTask1
zoneTask2
drainMicroTaskQueue
zone.scheduleMacroTask('name', callback,
data, customSchedule, customCancel);
zone.scheduleEventTask('name', callback,
data, customSchedule, customCancel);
Zone.__symbol__ // will return a string starts with __symbol__
Zone.__symbol__('test') === '__zone_symbol__test';
Zone.__load_patch('myOwnAsyncApi',
(global, Zone, api) => {});
// Save the original reference to setTimeout
let originalSetTimeout = window.setTimeout;
// Overwrite the API with a function which wraps callback in zone.
window.setTimeout = function(callback, delay) {
// Invoke the original API but wrap the callback in zone.
return originalSetTimeout(
// Wrap the callback method
Zone.current.wrap(callback),
delay
);
}
function main () {
b1.addEventListener('click', bindSecondButton);
}
/*
* What if your stack trace could tell you what
* order the user pushed the buttons from the stack trace?
* What if you could log this back to the server?
* Think of how much more productive your debugging could be!
*/
function bindSecondButton () {
b2.addEventListener('click', throwError);
}
function throwError () {
throw new Error('aw shucks');
}
main();
function btnClicked () {
recurRandonGenerateTimeout(10);
}
function recurRandonGenerateTimeout (x) {
if (x > 0) {
setTimeout(function () {
for (var i = x; i < 8; i++) {
recur(x - 1, Math.random()*100);
}
}, t);
}
}
function btnClicked () {
asyncHeavyWork1();
asyncHeavyWork2();
asyncHttpRequest();
}
const fs = require('fs');
fs.open('./test.txt', 'r', (err, fd) => {
fs.fstat(fd, (err, stat) => {
doSomething(fd, err => {});
doSomethingElse(fd, err => {});
// when to release fd?
});
});
viewBtnClicked() {
httpRequest(viewUrl);
httpRequest(additionalInfoUrl);
}
orderBtnClicked() {
httpRequest(orderUrl);
httpRequest(transactionUrl);
}
errorBtnClicked() {
throw new Error();
}
function httpBtnClicked() {
httpRequestUrl(viewUrl);
}
function timeoutBtnClicked() {
setTimeout(() => {
data.timeout = 'timeout';
});
}
function addBtnClicked() {
if (!data.num) {
data.num = 0;
}
data.num ++;
}
const api = require('./async-lib');
describe('testAsync', () => {
it('test async operation', (done) => {
let a = 0;
api.testAsync(a, r => {
expect(r).toBe(1);
done();
});
});
});
const api = require('./async-lib');
describe('testAsync', async(() => {
it('test async operation', () => {
let a = 0;
api.testAsync(a, r => {
expect(r).toBe(1);
});
});
}));
describe('testAsync', () => {
it('test long async operation', (done) => {
setTimeout(() => {
// ... expect something.
done();
}, 10000);
});
});
describe('testAsync', () => {
it('test long async operation', fakeAsync(() => {
setTimeout(() => {
// ... expect something.
}, 10000);
tick(10000);
}));
});
describe('testAsync', () => {
it('async', async(() => {
}));
it('fakeAsync', fakeAsync(() => {
}));
});
global.it = function(testFn) {
runTestInProxyZone(testFn);
}
function async() {
const asyncTestZoneSpec = new AsyncTestZoneSpec();
getProxyZoneSpec().setDelegate(asyncTestZoneSpec);
}
ProxyZoneSpec
{delegate: delegateSpec,
onInvokeTask
...
}
realZoneSpec
onInvokeTask
NgZone
Tell when to trigger Change Detection
AsyncTest
FakeAsyncTest
SyncTest
Error Handling
Debug/Tracing
TaskTrackingZone
LongStackTraceZone
ngZone.run(() => {
// will be in angular zone
// will trigger change detection
});
ngZone.runOutsideAngular(() => {
// will be outside angular zone
// "will not trigger change detection"
});
wtfZoneSpec
TaskTrackingZoneSpec
longStackTraceZoneSpec
angular
Timer
Promise
EventTarget
...
requestAnimationFrame
(window as any).__Zone_disable_requestAnimationFrame = true;
(window as any).__Zone_disable_customElements = true;
// recommended variable name
__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];
// deprecated variable name
// __zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove'];
// ngZone.runOutsideAngular(() => {});
declare let Zone: any;
(window as any)[Zone.__symbol__('setTimeout')](() => {
// will not in zone.
}, 100);
ZoneAwarePromise
import 'zone.js/dist/zone-error';
import 'zone.js/dist/zone-error';
class MyError extends Error {
}
try {
throw new MyError('myError');
} catch (error: Error) {
if (error instanceof MyError) {
// do some special error handling
}
}
(window as any).__Zone_Error_BlacklistedStackFrames_policy
= 'disable';
import 'zone.js/dist/zone-mix';
import 'zone.js/dist/zone-patch-electron';
MediaQuery
Notification
RTCPeerConnction
ShadyDom
Cordova
ResizeObserver
SocketIO
UserMedia
Jsonp Helper
will support performance api later
it('should advance Date with tick in fakeAsync',
fakeAsync(() => {
const start = Date.now();
tick(100);
const end = Date.now();
expect(end - start).toBe(100);
}));
beforeEach(() => {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should get date diff correctly', () => {
// automatically run into fake async zone,
const start = Date.now();
jasmine.clock().tick(100);
const end = Date.now();
expect(end - start).toBe(100);
});
__zone_symbol__fakeAsyncAutoFakeAsyncWhenClockPatched
import 'zone.js/dist/zone-patch-rxjs-fake-async';
it('rxjs scheduler should be advanced by tick in fakeAsync',
fakeAsync(() => {
observable.delay(1000).subscribe(v => {
result = v;
});
expect(result).toBeNull();
testZoneSpec.tick(1000);
expect(result).toBe('hello');
}));
it('should timeout', async(() => {
setTimeout(() => {}, 10000);
fixture.whenStable(() => {});
}));
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
--Pendng async tasks are: [type: macroTask, source: setTimeout, args: {handleId:3,isPeriodic:false,delay:10000,args:[]}
<script src="zone.js/dist/zone-evergreen.js"
type="module"></script>
<script src="zone.js/dist/zone.js" nomodule></script>
<div (click)="doSomethingElse()">
<button (click)="doSomething()">click me</button>
</div>
platformBrowserDynamic()
.bootstrapModule(
AppModule,
{
ngZoneEventCoalescing: true
}
)
button.addEventListener('click', click1);
button.addEventListener('click', click2);
button.addEventListener('mousedown', mousedown1);
const clickListeners = button.eventListeners('click');
// will get click1 and click2
button.removeAllListeners('click');
// will remove click1 and click2
button.removeAllListeners();
// will remove all listeners for all events