Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
test(task): add testcases for task lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaLiPassion committed Mar 12, 2017
1 parent e3fee8f commit f54dc52
Show file tree
Hide file tree
Showing 9 changed files with 1,172 additions and 46 deletions.
Binary file modified doc/error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions doc/error.puml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@startuml
scheduling --> throw: zoneSpec.onScheduleTask\nor task.scheduleFn\nthrow error
scheduling --> unknown: zoneSpec.onScheduleTask\nor task.scheduleFn\nthrow error
running --> scheduled: error in \ntask.callback\nand task is\nperiodical\ntask
running --> notScheduled: error in\ntask.callback\nand\ntask is not\nperiodical
running: zoneSpec.onHandleError
running --> throw: error in\n task.callback\n and \nzoneSpec.onHandleError\n return true
throw: throw to application
canceling --> throw: zoneSpec.onCancelTask\n or task.cancelFn\n throw error
canceling --> unknown: zoneSpec.onCancelTask\n or task.cancelFn\n throw error
unknown --> throw
@enduml
File renamed without changes
File renamed without changes.
2 changes: 1 addition & 1 deletion doc/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ EventTask will go back to scheduled state after invoked(running state), and will
Such as setTimeout/XMLHttpRequest, their lifecycle(state transition)
looks like this.

![onetime-MacroTask](onetime-macrotask.png "onetime MacroTask")
![non-periodical-macroTask](non-periodical-macrotask.png "non periodical macroTask")

ZoneSpec's onHasTask callback will be triggered when the first macroTask were scheduled or the
last macroTask was invoked or cancelled.
Expand Down
88 changes: 68 additions & 20 deletions lib/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,9 @@ type HasTaskState = {
type TaskType = 'microTask'|'macroTask'|'eventTask';

/**
* Task type: `notScheduled`, `scheduling`, `scheduled`, `running`, `canceling`.
* Task type: `notScheduled`, `scheduling`, `scheduled`, `running`, `canceling`, 'unknown'.
*/
type TaskState = 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling';
type TaskState = 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling'|'unknown';


/**
Expand Down Expand Up @@ -514,7 +514,7 @@ interface Task {
type: TaskType;

/**
* Task state: `notScheduled`, `scheduling`, `scheduled`, `running`, `canceling`.
* Task state: `notScheduled`, `scheduling`, `scheduled`, `running`, `canceling`, `unknown`.
*/
state: TaskState;

Expand Down Expand Up @@ -559,7 +559,7 @@ interface Task {
* @type {Zone} The zone which will be used to invoke the `callback`. The Zone is captured
* at the time of Task creation.
*/
zone: Zone;
readonly zone: Zone;

/**
* Number of times the task has been executed, or -1 if canceled.
Expand Down Expand Up @@ -614,7 +614,7 @@ const Zone: ZoneType = (function(global: any) {
const NO_ZONE = {name: 'NO ZONE'};
const notScheduled: 'notScheduled' = 'notScheduled', scheduling: 'scheduling' = 'scheduling',
scheduled: 'scheduled' = 'scheduled', running: 'running' = 'running',
canceling: 'canceling' = 'canceling';
canceling: 'canceling' = 'canceling', unknown: 'unknown' = 'unknown';
const microTask: 'microTask' = 'microTask', macroTask: 'macroTask' = 'macroTask',
eventTask: 'eventTask' = 'eventTask';

Expand Down Expand Up @@ -754,29 +754,50 @@ const Zone: ZoneType = (function(global: any) {
}
}
} finally {
if (task.type == eventTask || (task.data && task.data.isPeriodic)) {
// if the task's state is notScheduled, then it has already been cancelled
// we should not reset the state to scheduled
if (task.state !== notScheduled) {
// if the task's state is notScheduled or unknown, then it has already been cancelled
// we should not reset the state to scheduled
if (task.state !== notScheduled && task.state !== unknown) {
if (task.type == eventTask || (task.data && task.data.isPeriodic)) {
reEntryGuard && (task as ZoneTask<any>)._transitionTo(scheduled, running);
} else {
task.runCount = 0;
this._updateTaskCount(task as ZoneTask<any>, -1);
reEntryGuard &&
(task as ZoneTask<any>)._transitionTo(notScheduled, running, notScheduled);
}
} else {
task.runCount = 0;
this._updateTaskCount(task as ZoneTask<any>, -1);
reEntryGuard &&
(task as ZoneTask<any>)._transitionTo(notScheduled, running, notScheduled);
}
_currentZoneFrame = _currentZoneFrame.parent;
_currentTask = previousTask;
}
}

scheduleTask<T extends Task>(task: T): T {
if (task.zone && task.zone !== this) {
// check if the task was rescheduled, the newZone
// should not be the children of the original zone
let newZone: any = this;
while (newZone) {
if (newZone === task.zone) {
throw Error(`can not reschedule task to ${this
.name} which is descendants of the original zone ${task.zone.name}`);
}
newZone = newZone.parent;
}
}
(task as any as ZoneTask<any>)._transitionTo(scheduling, notScheduled);
const zoneDelegates: ZoneDelegate[] = [];
(task as any as ZoneTask<any>)._zoneDelegates = zoneDelegates;
task.zone = this;
task = this._zoneDelegate.scheduleTask(this, task) as T;
(task as any as ZoneTask<any>)._zone = this;
try {
task = this._zoneDelegate.scheduleTask(this, task) as T;
} catch (err) {
// should set task's state to unknown when scheduleTask throw error
// because the err may from reschedule, so the fromState maybe notScheduled
(task as any as ZoneTask<any>)._transitionTo(unknown, scheduling, notScheduled);
// TODO: @JiaLiPassion, should we check the result from handleError?
this._zoneDelegate.handleError(this, err);
throw err;
}
if ((task as any as ZoneTask<any>)._zoneDelegates === zoneDelegates) {
// we have to check because internally the delegate can reschedule the task.
this._updateTaskCount(task as any as ZoneTask<any>, 1);
Expand Down Expand Up @@ -809,8 +830,22 @@ const Zone: ZoneType = (function(global: any) {
}

cancelTask(task: Task): any {
if (task.zone != this)
throw new Error(
'A task can only be cancelled in the zone of creation! (Creation: ' +
(task.zone || NO_ZONE).name + '; Execution: ' + this.name + ')');
(task as ZoneTask<any>)._transitionTo(canceling, scheduled, running);
this._zoneDelegate.cancelTask(this, task);
try {
this._zoneDelegate.cancelTask(this, task);
} catch (err) {
// if error occurs when cancelTask, transit the state to unknown
(task as ZoneTask<any>)._transitionTo(unknown, canceling);
// TODO: @JiaLiPassion, should updateTaskCount when cancelTask throw error?
this._updateTaskCount(task as ZoneTask<any>, -1);
this._zoneDelegate.handleError(this, err);
throw err;
}
// TODO: @JiaLiPassion, should updateTaskCount when cancelTask throw error?
this._updateTaskCount(task as ZoneTask<any>, -1);
(task as ZoneTask<any>)._transitionTo(notScheduled, canceling);
task.runCount = 0;
Expand Down Expand Up @@ -1025,14 +1060,23 @@ const Zone: ZoneType = (function(global: any) {
value = this._cancelTaskZS.onCancelTask(
this._cancelTaskDlgt, this._cancelTaskCurrZone, targetZone, task);
} else {
if (!task.cancelFn) {
throw Error('Task is not cancelable');
}
value = task.cancelFn(task);
}
return value;
}

hasTask(targetZone: Zone, isEmpty: HasTaskState) {
return this._hasTaskZS &&
this._hasTaskZS.onHasTask(this._hasTaskDlgt, this._hasTaskCurrZone, targetZone, isEmpty);
// hasTask should not throw error so other ZoneDelegate
// can still trigger hasTask callback
try {
return this._hasTaskZS &&
this._hasTaskZS.onHasTask(
this._hasTaskDlgt, this._hasTaskCurrZone, targetZone, isEmpty);
} catch (err) {
}
}

_updateTaskCount(type: TaskType, count: number) {
Expand Down Expand Up @@ -1064,7 +1108,7 @@ const Zone: ZoneType = (function(global: any) {
public data: TaskData;
public scheduleFn: (task: Task) => void;
public cancelFn: (task: Task) => void;
public zone: Zone = null;
_zone: Zone = null;
public runCount: number = 0;
_zoneDelegates: ZoneDelegate[] = null;
_state: TaskState = 'notScheduled';
Expand Down Expand Up @@ -1093,6 +1137,10 @@ const Zone: ZoneType = (function(global: any) {
};
}

get zone(): Zone {
return this._zone;
}

get state(): TaskState {
return this._state;
}
Expand Down
Loading

0 comments on commit f54dc52

Please sign in to comment.