Learn basic Zone.js by examples
@JiaLiPassion
Who am I
- Name: Jia Li
- Company: ThisDot
- Zone.js: Code Owner
- Angular: Collaborator
Agenda
- What's Zone.js
- When and why you need Zone.js
- Zone.js in Angular
What is Zone.js
A Zone is an execution context that persists across async tasks.
Created by Brian Ford inspired by Dart.
What zone.js can do
-
Provide execution context
-
Provide async task lifecycle hook
-
Provide error handler for async operations
What is execution context
execution context is an abstract concept that holds information about the environment within which the current code is being executed
Execution Context
const globalThis = this;
function testFunc() {
console.log('this in testFunc is:', this === globalThis);
}
testFunc();
Execution Context
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();
Execution Context in JS
- When code is executed in a function, there will be a new execution context
- Determine Scope (variables and functions the function can access)
- Determine the value of `this`.
Execution Context in JS
function c() {
console.log(new Error('error in c'));
}
function b() {
c.apply({});
}
function a() {
b.call({});
}
a();
// Error: error in c
// at c (js-error.html:4)
// at b (js-error.html:8)
// at a (js-error.html:12)
Execution Context in JS
function c() {
console.log(new Error('error in c'));
}
function timeout() {
setTimeout(c);
}
timeout();
// Error: error in c
// at c (js-error.html:4)
Missing features of execution context of JS
- No execution context across functions
Execution context in zone.js
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);
});
});
Execution Context in Zone.js
- It looks like we have a new `zoneThis` magic keyword like `this`.
- for sync operation, `zoneThis` will be the zone it is running in.
- for async operation, `zoneThis` will be the zone it is scheduled.
How to get `zoneThis`
zone.run(function() {
expect(Zone.current).toBe(zone);
setTimeout(function() {
expect(Zone.current).toBe(zone);
});
});
LifeCycle Interceptable Hooks
- onFork
- onIntercept
- onInvoke
- onHandleError
- onScheduleTask
- onInvokeTask
- onCancelTask
- onHasTask
setTimeout
setTimeout(function() {
console.log('callback invoked');
}, 1000);
Async in Javascript Engine
// Javascirpt 101
function a() {}
function b() {}
function c() {}
function d() {}
a();
setTimeout(c, 100);
setTimeout(d, 100);
b();
// run order
// 1. a
// 2. b
// 3. c
// 4. d
performance counter
// Javascirpt 101
function a() {// cost 100ms }
function b() {// cost 100ms }
function c() {// cost 100ms }
function d() {// cost 100ms }
performance.start();
a();
setTimeout(c, 100);
setTimeout(d, 200);
b();
performance.end();
Async in Javascript
Time
a
b
c
d
performance.start()
performance.end()
setTimeout in Zone
var zone = Zone.current.fork({
name: 'hook',
onScheduleTask: ...,
onInvokeTask: ....,
onHasTask: ...
});
zone.run(() => {
setTimeout(() => {
console.log('timer callback invoked');
}, 100);
});
Zone
zoneA = {
onInvokeTask: (callback) => {
performance.start();
callback();
performance.end();
}
};
zoneA.run(() => {
performance.start();
a();
setTimeout(c);
setTimeout(d);
b();
performance.end();
});
setTimeout(e);
Zone
Time
a
b
c
performance.start()
performance.end()
performance.start()
performance.end()
d
performance.start()
performance.end()
e
zoneA
zoneA
zoneA
zoneA
Async Error Handling
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');
});
});
Monkey-Patch
const api = {
add: function add(a, b) {
return a + b;
}
}
api.add(1, 2); // will return 3;
const originalAdd = api.add;
api.add = function() {
console.log('api.add is called');
return originalAdd.apply(this, arguments);
}
api.add(1, 2); // will log and return 3;
When should we use Zone
- Test
- Sync(Disallow Async)
- Async(Auto done, Auto cleanup)
- FakeAsync()
- Debug
- LongStackTrace
- Task Tracing
- Performance measure
- Framework AutoRender
- User Action Tracing
- Resource Releasing
Demo: LongStackTrace
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();
Demo: Tracking: Counting
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);
}
}
Performance Profiling
function btnClicked () {
asyncHeavyWork1();
asyncHeavyWork2();
asyncHttpRequest();
}
Auto releasing
const fs = require('fs');
fs.open('./test.txt', 'r', (err, fd) => {
if (err) throw err;
fs.fstat(fd, (err, stat) => {
if (err) throw err;
doSomething(fd, err => {});
doSomethingElse(fd, err => {});
});
});
const autoReleaseFSZone = Zone.current.fork({
name: 'releaseFS',
onHasTask: (pd, curr, target, hasTaskState) => {
if (!hasTaskState.macroTask && !hasTaskState.microTask && fd !== -1) {
fs.close(fd, (err) => {
if (err) throw err;
fd = -1;
});
}
return pd.hasTask(target, hasTaskState);
}
});
User Action Tracking
viewBtnClicked() {
httpRequest(viewUrl);
httpRequest(additionalInfoUrl);
}
orderBtnClicked() {
httpRequest(orderUrl);
httpRequest(transactionUrl);
}
errorBtnClicked() {
throw new Error();
}
UI Auto Rendering
function httpBtnClicked() {
httpRequestUrl(viewUrl);
}
function timeoutBtnClicked() {
setTimeout(() => {
data.timeout = 'timeout';
});
}
function addBtnClicked() {
if (!data.num) {
data.num = 0;
}
data.num ++;
}
Async Test
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);
});
});
}));
fakeAsync Test
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);
}));
});
Zone.js in Angular
NgZone
Tell when to trigger Change Detection
AsyncTest
FakeAsyncTest
SyncTest
Error Handling
Debug/Tracing
TaskTrackingZone
LongStackTraceZone
ngZone
ngZone.run(() => {
// will be in angular zone
// will trigger change detection
});
ngZone.runOutsideAngular(() => {
// will be outside angular zone
// will not trigger change detection
});
Thank you!
Learn zone.js by examples
By jiali
Learn zone.js by examples
- 1,117