What's new in Zone.js
@JiaLiPassion
Who am I
- Name: LI JIA
- Company: Sylabs
- Zone.js: Collaborator
- Angular: Contributor
Agenda
- What's Zone.js
- Zone.js in Angular
- What's new in zone.js will help you 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 interceptable hook
-
Provide async error handler
Execution Context
Zone.current.fork({name: 'a'})
.run(function() {
setTimeout(function () {
// Zone.current is a
}, 100);
setTimeout(function() {
// Zone.current is a
}, 200);
});
function outside() {
// Execution context of outside
function inside1() {
// Execution context of inside1
}
function inside2() {
// Execution context of inside2
}
}
LifeCycle Interceptable Hooks
- onFork
- onIntercept
- onInvoke
- onHandleError
- onScheduleTask
- onInvokeTask
- onCancelTask
- onHasTask
setTimeout
setTimeout(function() {
console.log('callback invoked');
}, 1000);
setTimeout in Zone
var zone = Zone.current.fork({
name: 'hook',
onScheduleTask: ...,
onInvokeTask: ....,
onHasTask: ...
});
zone.run(() => {
setTimeout(() => {
console.log('timer callback invoked');
}, 100);
});
Zone.js Event loop Demo
https://zone-ebb17.firebaseapp.com/
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');
});
});
Implementation: Mokey-Patch
const nativeTimeout = window.setTimeout;
window.setTimeout = function(callback) {
const zone = Zone.current;
const wrappedCallback = function() {
zone.onInvokeTask('setTimeout'); // invoke hook
return callback.apply(this, arguments);
}
zone.onScheduleTask('setTimeout'); // schedule hook
nativeTimeout.apply(this, wrappedCallback);
}
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
Zone.js in Angular
Change Detection
AsyncTest
FakeAsyncTest
SyncTest
Error Handling
Debug/Tracing
Zone.js in Angular
DOM API
setTimeout
Promise
EventTarget
...
Zone.js
Monkey-Patch
Angular
What's new
- Performance
- Promise
- Error handling
- Electron
- More APIs support
- Test
- Angular Elements (Proposal)
Performance
- Modularization
- Event
- Native Delegate
Modularization
Timer
Promise
EventTarget
requestAnimationFrame
...
Notification
MediaQuery
UserMedia
...
requestAnimationFrame
__Zone_disable_requestAnimationFrame=true
Event
__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove'];
@HostListener('window:mousemove.nozone')
mouseMoveListener() {
// will not in ngZone.
}
<div (scroll.nozone)="scrollHandler();"></div>
Proposal?
Native Delegate
declare let Zone: any;
(window as any)[Zone.__symbol__('setTimeout')](() => {
// will not in zone.
}, 100);
Promise
- Pass Promise A+ Test
- Support Promise.prototype.finally
- Support Bluebird
- All Bluebird specified APIs will in zone
- No more 'Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten ' error.
- No more 'Zone already loaded' error. (proposal)
import 'zone.js/dist/zone';
import 'core-js/promise';
Error Handling
import 'zone.js/dist/zone-error';
Error Handling
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'; // 'default', 'lazy'
Electron
import 'zone.js/dist/zone-mix';
import 'zone.js/dist/zone-patch-electron';
- Browser
- NodeJs
- Menu
- Screenshot
- Shell
- Main<->Render communication
More APIs Support
-
MediaQuery
-
Notification
-
RTCPeerConnction
-
ShadyDom
-
Cordova
-
ResizeObserver
-
SocketIO
-
UserMedia
-
Jsonp Helper
Test
- Bundle
- FakeAsync enhancement
- Async enhancement
- Jasmine/Mocha
- Unified Test Lib
Zone.js can be a standalone test library.
Test Bundle
import 'zone.js/dist/long-stack-trace';
import 'zone.js/dist/proxy';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async';
import 'zone.js/dist/promise-testing';
import 'zone.js/dist/zone-testing';
Test Bundle(Proposal)
import 'zone.js/dist/zone-testing';
describe('zone-testing', () => {
it('fakeAsync', fakeAsyncTest(() => {
tick(100);
discardPeriodicTasks();
flushMicrotasks();
});
it ('async', asyncTest(() => {}));
});
FakeAsync: Date.now
it('should advance Date when tick in fakeAsync', fakeAsync(() => {
const start = Date.now();
tick(100);
const end = Date.now();
expect(end - start).toBe(100);
}));
FakeAsync: jasmine.clock
beforeEach(() => {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should get date diff correctly', () => { // we don't need fakeAsync here.
// automatically run into fake async zone, because jasmine.clock() is installed.
const start = Date.now();
jasmine.clock().tick(100);
const end = Date.now();
expect(end - start).toBe(100);
});
__zone_symbol__fakeAsyncPatchLock
FakeAsync: rxjs.scheduler
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');
}));
Better timeout message
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:[]}
Jasmine 3.x/Mocha 5.x
Unified zone-testing
- Can auto detect Test Framework
- Can write mixed style test cases at the same time.
- Jasmine
- mocha
- jest
Unify TDD/BDD I/F
describe('BDD style', () => {});
xdescribe('BDD style', () => {});
suite('mocha style', () => {});
describe('', () => {
beforeEach(() => {});
suiteSetup(() => {});
before(() => {});
beforeAll(() => {});
});
describe('', () => {
it('case', () => {});
xit('case', () => {});
it.skip('mocha skip', () => {});
it.only('mocha only', () => {});
specify('mocha specify', () => {});
});
Jasmine | Mocha | Jest |
---|---|---|
beforeAll | before | beforeAll |
afterAll | after | afterAll |
xdescribe | describe.skip | describe.skip |
fdescribe | describe.only | describe.only |
xit | it.skip | it.skip |
fit | it.only | it.only |
Mocha BDD I/F
Specify, suite, suiteSetup, suiteTearDown...
Unify expect
it ('expect', () => {
expect(a).toEqual('test'); // jasmine
expect(b).toBeInstanceOf(BType); // jest
expect(Promise.resolve('lemon')).resolves.toBe('lemon'); // jest
const expected = [
expect.stringMatching(/^Alic/),
expect.stringMatching(/^[BR]ob/),
];
expect(['Alicia', 'Roberto', 'Evelina']).toEqual(expect.arrayContaining(expected)); // jest
const onPress = {x: 100, y: 200, z: 300};
expect(onPress).toEqual(expect.objectContaining({
x: expect.any(Number),
y: expect.any(Number),
}), ); // jest
// ...
});
Unify spy
it ('expect', async() => {
const spy = jasmine.createSpy();
const spy1 = spyOn(obj);
const spy2 = jest.mockFn();
const myMockFn = jest.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call'); // jest
const asyncMock = jest.fn()
.mockResolvedValue('default');
expect(await asyncMock()).toEqual('default'); // jest
});
Unify Fake Timer
describe('', () => {
beforeEach(() => jasmine.clock().install);
afterEach(() => jasmine.clock().uninstall);
});
describe('', () => {
beforeEach(() => {jest.useFakeTimers()});
afterEach(() => {jest.resetFakeTimers();});
});
Angular Elements (Scoped Zone Proposal)
Web App
JQuery
React
Angular Elements
Zone.js
Zone.js
Zone.js
Update recently for Angular Elements
- Custom Element V1 Support
- Support loading Zone.js multiple times
- Optimized root zone
-
rxjs 6/typescript 3
Update recently for Angular Elements
async function test() {
return 1;
}
async function main() {
console.log('before await', Zone.current.name);
await test();
console.log('after await', Zone.current.name);
}
Zone.current.fork({name: 'test'}).run(main);
Thank You!
- https://github.com/angular/zone.js
- @JiaLiPassion
devtv
By jiali
devtv
- 2,323