Zone.js Internals

@JiaLiPassion

Who am I

  • Name: Jia Li
  • Company: ThisDot
  • Zone.js: Code Owner
  • Angular: Collaborator

Agenda

  • Zone.js internal implementation
  • Zone.js in Angular
  • Some flags 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 across Async operations

Zone.current.fork({name: 'zone', properties: {key: 'value'}})
   .run(function() {
      // the callback of async operations will remember the context
      // when it is scheduled
      console.log(Zone.current.name, Zone.current.get('key')); 
      // zone value
      setTimeout(function () {
        console.log(Zone.current.name, Zone.current.get('key')); 
        // zone value
      }, 100);
      const p = Promise.resolve(1);
      p.then(() => {
        console.log(Zone.current.name, Zone.current.get('key')); 
        // zone value
      });
});

Point 1: Zone.current

zoneA.run(() => {
  const currentZoneA = Zone.current; // will be zoneA
  zoneB.run(() => {
    const currentZoneB = Zone.current; // will be zoneB
  });
});

Point 2: root zone

const rootZone = Zone.current; // will be <root> zone
console.log(`root zone's name is: ${rootZone.name}`);
// will be <root>

Point 3: ZoneSpec

const rootZone = Zone.current; // will be <root> zone
console.log(`root zone's name is: ${rootZone.name}`);
// will be <root>

LifeCycle Interceptable Hooks

  • onFork
  • onIntercept
  • onInvoke
  • onHandleError
  • onScheduleTask
  • onInvokeTask
  • onCancelTask
  • onHasTask

setTimeout

zone.run(() => {
  // Here the onScheduleTask will be invoked
  // And onHasTask will also be invoked
  setTimeout(function() {
    // Here the onInvokeTask will be invoked
    console.log('callback invoked');
  } // Here the onHasTask will be invoked again,
  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

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

Thank you!

Miško

     Takeshi Kondo

Thank You!

  • https://github.com/angular/zone.js
  • @JiaLiPassion

Zone.js Internals

By jiali

Zone.js Internals

  • 865