feat(zone): add initial implementation of VmTurnZone

This commit is contained in:
vsavkin 2014-12-05 18:30:45 -08:00
parent 4a08bbf7f1
commit df36ffb11d
14 changed files with 269 additions and 5 deletions

View File

@ -19,6 +19,7 @@ module.exports = function(config) {
// Including systemjs because it defines `__eval`, which produces correct stack traces.
'node_modules/systemjs/dist/system.src.js',
'node_modules/systemjs/lib/extension-register.js',
'node_modules/zone.js/zone.js',
'tools/build/file2modulename.js',
'test-main.js'

View File

@ -83,6 +83,7 @@ export function bootstrap(appComponentType: Type, bindings=null) {
var appInjector = _rootInjector.createChild(_injectorBindings(
appComponentType));
if (isPresent(bindings)) appInjector = appInjector.createChild(bindings);
return appInjector.asyncGet(ChangeDetector).then((cd) => {
// TODO(rado): replace with zone.
cd.detectChanges();

View File

@ -0,0 +1,66 @@
library angular.zone;
import 'dart:async' as async;
class VmTurnZone {
Function _onTurnStart;
Function _onTurnDone;
Function _onScheduleMicrotask;
async.Zone _outerZone;
async.Zone _innerZone;
int _nestedRunCounter;
VmTurnZone() {
_nestedRunCounter = 0;
_outerZone = async.Zone.current;
_innerZone = _outerZone.fork(specification: new async.ZoneSpecification(
run: _onRun,
runUnary: _onRunUnary,
scheduleMicrotask: _onMicrotask
));
}
initCallbacks({Function onTurnStart, Function onTurnDone, Function onScheduleMicrotask}) {
this._onTurnStart = onTurnStart;
this._onTurnDone = onTurnDone;
this._onScheduleMicrotask = onScheduleMicrotask;
}
dynamic run(fn()) => _innerZone.run(fn);
dynamic runOutsideAngular(fn()) => _outerZone.run(fn);
dynamic _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) {
_nestedRunCounter++;
try {
if (_nestedRunCounter == 1 && _onTurnStart != null) delegate.run(zone, _onTurnStart);
return fn();
} finally {
_nestedRunCounter--;
if (_nestedRunCounter == 0 && _onTurnDone != null) _finishTurn(zone, delegate);
}
}
dynamic _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) =>
_onRunBase(self, delegate, zone, () => delegate.run(zone, fn));
dynamic _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn(args), args) =>
_onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args));
void _finishTurn(zone, delegate) {
delegate.run(zone, _onTurnDone);
}
_onMicrotask(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn) {
if (this._onScheduleMicrotask != null) {
this._onScheduleMicrotask(fn);
} else {
delegate.scheduleMicrotask(zone, fn);
}
}
}

View File

@ -0,0 +1,52 @@
import {List, ListWrapper} from 'facade/collection';
import {normalizeBlank} from 'facade/lang';
export class VmTurnZone {
_outerZone;
_innerZone;
_onTurnStart:Function;
_onTurnDone:Function;
_nestedRunCounter:number;
constructor() {
this._nestedRunCounter = 0;
this._onTurnStart = null;
this._onTurnDone = null;
this._outerZone = window.zone;
this._innerZone = this._outerZone.fork({
beforeTask: () => this._beforeTask(),
afterTask: () => this._afterTask()
});
}
initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask} = {}) {
this._onTurnStart = normalizeBlank(onTurnStart);
this._onTurnDone = normalizeBlank(onTurnDone);
}
run(fn) {
return this._innerZone.run(fn);
}
runOutsideAngular(fn) {
return this._outerZone.run(fn);
}
_beforeTask(){
this._nestedRunCounter ++;
if(this._nestedRunCounter === 1 && this._onTurnStart) {
this._onTurnStart();
}
}
_afterTask(){
this._nestedRunCounter --;
if(this._nestedRunCounter === 0 && this._onTurnDone) {
this._onTurnDone();
}
}
}

View File

@ -0,0 +1,85 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, async, tick} from 'test_lib/test_lib';
import {Log, once} from 'test_lib/utils';
import {PromiseWrapper} from 'facade/async';
import {BaseException} from 'facade/lang';
import {VmTurnZone} from 'core/zone/vm_turn_zone';
export function main() {
describe("VmTurnZone", () => {
var log, zone;
beforeEach(() => {
log = new Log();
zone = new VmTurnZone();
zone.initCallbacks({
onTurnStart: log.fn('onTurnStart'),
onTurnDone: log.fn('onTurnDone')
});
});
describe("run", () => {
it('should call onTurnStart and onTurnDone', () => {
zone.run(log.fn('run'));
expect(log.result()).toEqual('onTurnStart; run; onTurnDone');
});
it('should return the body return value from run', () => {
expect(zone.run(() => 6)).toEqual(6);
});
it('should not run onTurnStart and onTurnDone for nested Zone.run', () => {
zone.run(() => {
zone.run(log.fn('run'));
});
expect(log.result()).toEqual('onTurnStart; run; onTurnDone');
});
it('should call onTurnStart and onTurnDone before and after each top-level run', () => {
zone.run(log.fn('run1'));
zone.run(log.fn('run2'));
expect(log.result()).toEqual('onTurnStart; run1; onTurnDone; onTurnStart; run2; onTurnDone');
});
it('should call onTurnStart and onTurnDone before and after each turn', (done) => {
var a = PromiseWrapper.completer();
var b = PromiseWrapper.completer();
zone.run(() => {
log.add('run start');
a.promise.then((_) => log.add('a then'));
b.promise.then((_) => log.add('b then'));
});
a.complete("a");
b.complete("b");
PromiseWrapper.all([a.promise, b.promise]).then((_) => {
expect(log.result()).toEqual('onTurnStart; run start; onTurnDone; onTurnStart; a then; onTurnDone; onTurnStart; b then; onTurnDone');
done();
});
});
});
describe("runOutsideAngular", () => {
it("should run a function outside of the angular zone", () => {
zone.runOutsideAngular(log.fn('run'));
expect(log.result()).toEqual('run');
});
});
describe("exceptions", () => {
it('should rethrow exceptions from the body', () => {
expect(() => {
zone.run(() => {
throw new BaseException('hello');
});
}).toThrowError('hello');
});
});
});
}

View File

@ -20,4 +20,17 @@ class PromiseWrapper {
if (success == null) return promise.catchError(onError);
return promise.then(success, onError: onError);
}
static completer(){
return new _Completer(new Completer());
}
}
class _Completer {
Completer c;
_Completer(this.c);
get promise => c.future;
get complete => c.complete;
}

View File

@ -17,4 +17,20 @@ export class PromiseWrapper {
static then(promise:Promise, success:Function, rejection:Function):Promise {
return promise.then(success, rejection);
}
static completer() {
var resolve;
var reject;
var p = new Promise(function(res, rej) {
resolve = res;
reject = rej;
});
return {
promise: p,
complete: resolve,
reject: reject
};
}
}

View File

@ -97,6 +97,7 @@ class ListWrapper {
static void insert(List l, int index, value) { l.insert(index, value); }
static void removeAt(List l, int index) { l.removeAt(index); }
static void clear(List l) { l.clear(); }
static String join(List l, String s) => l.join(s);
}
bool isListLikeIterable(obj) => obj is Iterable;

View File

@ -121,11 +121,16 @@ export class ListWrapper {
list.splice(index, 0, value);
}
static removeAt(list, index:int) {
var res = list[index];
list.splice(index, 1);
return res;
}
static clear(list) {
list.splice(0, list.length);
}
static join(list, s) {
return list.join(s);
}
}
export function isListLikeIterable(obj):boolean {

View File

@ -77,7 +77,6 @@ window.beforeEach(function() {
});
});
function mapToString(m) {
if (!m) {
return ''+m;

View File

@ -0,0 +1,23 @@
import {List, ListWrapper} from 'facade/collection';
export class Log {
_result:List;
constructor() {
this._result = [];
}
add(value) {
ListWrapper.push(this._result, value);
}
fn(value) {
return () => {
ListWrapper.push(this._result, value);
}
}
result() {
return ListWrapper.join(this._result, "; ");
}
}

View File

@ -1,5 +1,6 @@
import {describe, it, iit, ddescribe, expect} from 'test_lib/test_lib';
import {MapWrapper} from 'facade/collection';
import {describe, it, iit, ddescribe, expect, tick, async} from 'test_lib/test_lib';
import {MapWrapper, ListWrapper} from 'facade/collection';
import {PromiseWrapper} from 'facade/async';
class TestObj {
prop;

View File

@ -25,7 +25,8 @@
"systemjs": "^0.9.1",
"through2": "^0.6.1",
"traceur": "0.0.74",
"which": "~1"
"which": "~1",
"zone.js": "0.3.0"
},
"devDependencies": {
"bower": "^1.3.12",