源代码解读 - Zone.js 任务管理

Zone这个模块是用来管理任务异步调用的。

概览

在它的lib源代码目录下有很多文件夹。/lib/Zone.ts存放了zone的核心代码,其他的文件夹主要是对核心代码的拓展,测试以及兼容加强。

Zone的核心代码中主要用Zone,ZoneType,ZoneSpec,ZoneDelegate,Task这几个核心类。头部代码定义了各种接口,最后在底部代码一个变量名为Zone的闭包中,实现这些类,并初始化了许多内容。

Zone

Zone是一种用于拦截和跟踪异步工作的机制, 同时作为暴露出去的全局变量。

在Zone.ts文件中, Zone接口定义在初始化闭包之外,闭包内部有同名为Zone的类的实现。为了不混淆和重负使用命名,Zone初始化闭包都使用了AmbientZone和AmbientZoneDelegate的变量为接口名。

/** @internal */
type AmbientZone = Zone;
/** @internal */
type AmbientZoneDelegate = ZoneDelegate;
属性
  • parent
    Zone和Dom类似,有父子结构,可以通过parent递归调用

  • name
    用于标识每个不同的zone

静态方法
  • __symbol__
    Zone内部很多全局变量的名字都会通过者这个方法加入global['__Zone_symbol_prefix']或'zone_symbol'为前缀,避免全局变量污染。
const symbolPrefix = global['__Zone_symbol_prefix'] || '__zone_symbol__';

function __symbol__(name: string) {
  return symbolPrefix + name;
}
  • __load_patch
    __load_patch 是用来为Zone增强功能补丁的方法,和__symbol__一样,源码还加入了备注忽略tslint的下划线检查。代码头部根据需求检查是否重复加载或者重名,没有的情况下,通过fn(global, Zone, _api)的方法加载补丁。mark和performanceMeasure都是用来测试性能节点的。
// tslint:disable-next-line:require-internal-with-underscore
static __load_patch(name: string, fn: _PatchFn, ignoreDuplicate = false): void {
  if (patches.hasOwnProperty(name)) {
    // `checkDuplicate` option is defined from global variable
    // so it works for all modules.
    // `ignoreDuplicate` can work for the specified module
    if (!ignoreDuplicate && checkDuplicate) {
      throw Error('Already loaded patch: ' + name);
    }
  } else if (!global['__Zone_disable_' + name]) {
    const perfName = 'Zone:' + name;
    mark(perfName);
    patches[name] = fn(global, Zone, _api);
    performanceMeasure(perfName, perfName);
  }
}
  • assertZonePatched
    检查是否重复加载了Zone, 重复加载会报错
只读静态属性
  • root
    从Zone.current开始,遍历找到顶部Zone。

  • current
    返回_currentZoneFrame中的zone

  • currentTask
    返回_currentTask

方法
  • getZoneWith
    通过源码,我们可以看到getZoneWith方法会从方法的调用者开发向上递归,直到找到_properties有属性名为key的Zone,并返回Zone或者null(如果没找到)。_properties的定义在ZoneDelegate部分会另外叙述。
public getZoneWith(key: string): AmbientZone|null {
  let current: Zone|null = this;
  while (current) {
    if (current._properties.hasOwnProperty(key)) {
      return current;
    }
    current = current._parent;
  }
  return null;
}
  • get
    get方法内部调用了getZoneWith方法来遍历寻找Zone,并返回要查询的属性值。
public get(key: string): any {
  const zone: Zone = this.getZoneWith(key) as Zone;
  if (zone) return zone._properties[key];
}
  • fork
    public fork(zoneSpec: ZoneSpec): AmbientZone
    fork方法会要求传入一个zoneSpec,然后为当前Zone添加一个child zone。 如果没有传入Zonespec, 会直接报错。zone的fork方法会调用_zoneDelegate的fork方法,后然下面是zoneDelegate的fork代码片段。_forkZS在zoneDelegate初始化的时候,会判断有没有onFork方法,没有的话,会一直向上寻找,确保一定有onFork这个方法。然后就是能onFork就直接调用,不行的话就会new 一个Zone。
    onFork后的感叹号有两个作用,一个是闭包方法,还有一个是为方法返回一个true值。
fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone {
  return this._forkZS ? this._forkZS.onFork!(this._forkDlgt!, this.zone, targetZone, zoneSpec) :
                        new Zone(targetZone, zoneSpec);
}
  • wrap
    public wrap<T extends Function>(callback: T, source: string): T
    wrap方法传入的callback可以是继承了Function的泛型。source则是用与debug定位。zoneDelegate的intercept方法会封装一层callback,然后返回一个T类型的方法,在该返回方法中通过zone的runGuarded运行callback方法。

  • run
    public run<T>(callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T
    调用zoneDelegate的invoke方法,无谓是否报错都将_currentZoneFrame设置为_currentZoneFrame.parent。

  • runGuarded
    public runGuarded<T>(callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string)
    和run 相比, runGuarded多了一个catch调用handleError

catch (error) {
  if (this._zoneDelegate.handleError(this, error)) {
    throw error;
  }
}
  • runTask
    runTask(task: Task, applyThis?: any, applyArgs?: any): any
    通过设置Zone.currentTask, 最终调用this._zoneDelegate.invokeTask来执行task。执行代码前会临时保存当前的_currentTask和_currentZoneFrame,最终在finally代码块中恢复之前的_currentTask和_currentZoneFrame。
const previousTask = _currentTask;
_currentTask = task;
_currentZoneFrame = {parent: _currentZoneFrame, zone: this};
//... other code
_currentZoneFrame = _currentZoneFrame.parent!;
_currentTask = previousTask;
  • scheduleMicroTask
  • scheduleMacroTask
  • scheduleEventTask

这三个Schedule都会调用scheduleTask,不同的是MircoTask没有customCancel的传入

  • scheduleTask
    scheduleTask<T extends Task>(task: T): T
    调用this._zoneDelegate.scheduleTask

  • cancelTask
    cancelTask(task: Task): any
    调用this._zoneDelegate.cancelTask

ZoneType

ZoneType在头部定义接口以后主要有两个地方会用到,一个是底部代码执行闭包的类型const Zone: ZoneType = (function(global: any), 还有一个就是作为Zone增强补丁的参数类型type _PatchFn = (global: Window, Zone: ZoneType, api: _ZonePrivate) => void;

ZoneType的属性和方法,是Zone的静态属性和方法的调用或者增强。

  • current

  • currentTask

  • root

  • assertZonePatched
    assertZonePatched(): void;

  • __load_patch
    \_\_load_patch(name: string, fn: _PatchFn, ignoreDuplicate?: boolean): void;

  • __symbol__(name: string): string;
    __symbol__(name: string): string;

ZoneSpec

ZoneSpec为Zone配置事件

属性
  • name
    和Zone一样,用于Debug

  • properties
    配置Zone可以使用的属性,后期不能增删

事件
  • onFork
    onFork?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,zoneSpec: ZoneSpec) => Zone;
    在Zone.fork的时候调用

  • onIntercept
    onIntercept?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function, source: string) => Function;
    在Zone.wrap callback的时候调用

  • onInvoke
    onInvoke?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function,applyThis: any, applyArgs?: any[], source?: string) => any;
    在Zone.callback调用的时候调用

  • onHandleError
    onHandleError?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any) => boolean;
    在Zone invoke报错时调用,参考Zone.runGuarded

  • onScheduleTask
    onScheduleTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) => Task;
    在Zone.scheduleTask时调用

  • onInvokeTask
    onInvokeTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task, applyThis: any, applyArgs?: any[]) => any;
    在Zone.runTask时调用

  • onCancelTask
    onCancelTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) => any;
    在用Zone.cancelTask时调用

  • onHasTask
    onHasTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, hasTaskState: HasTaskState) => void;
    在zone中HasTask变化时调用

ZoneDelegate

ZoneDelegate可以拦截区域操作时的委托

private 变量
  • _parentDelegate 这是一个内部私有变量, 与Zone类中的parent类似,用于向上遍历,递归调用 。但ZoneDelegate的接口并没有要求_parentDelegate属性,可见一些增加类中,可以使用其他结构,递归调用。

  • _taskCounts
    Zone内部使用_taskCounts对象分别为microTask,macroTask和eventTask计数。

  • 私有代理方法
    以fork为例,ZoneDelegate分别有三个对象_forkDlgt,_forkZS,_forkCurrZone分别指向fork在ZoneDelegate,ZoneSpec和Zone中的调用者。这点适用于接口中所有的方法。

fork相关私有变量

private _forkDlgt: ZoneDelegate|null;
private _forkZS: ZoneSpec|null;
private _forkCurrZone: Zone|null;

hasTask私有相关变量多了一个_hasTaskDlgtOwner

private _hasTaskDlgt: ZoneDelegate|null;
private _hasTaskDlgtOwner: ZoneDelegate|null;
private _hasTaskZS: ZoneSpec|null;
private _hasTaskCurrZone: Zone|null;
constructor

constructor(zone: Zone, parentDelegate: ZoneDelegate|null, zoneSpec: ZoneSpec|null)
ZoneDelegate的构造函数有大量代码实现代理这一过程。

  • fork

没有zoneSpec,以下指针都为null

有onFork时:
_forkZS指向zoneSpec
_forkDlgt指向parentDelegate
_forkCurrZone指向this.zone

不然
_forkZS指向parentDelegate的_forkZS
_forkDlgt指向parentDelegate的_forkDlgt
_forkCurrZone指向parentDelegate的_forkCurrZone

以后指针同理,可以看到其实这地方代码结构高度重复,可以考虑写个help方法
_interceptZS
_invokeZS
_handleErrorZS
_scheduleTaskZS
_invokeTaskZS
_cancelTaskZS

this._forkZS = zoneSpec && (zoneSpec && zoneSpec.onFork ? zoneSpec : parentDelegate!._forkZS);
this._forkDlgt = zoneSpec && (zoneSpec.onFork ? parentDelegate : parentDelegate!._forkDlgt);
this._forkCurrZone = zoneSpec && (zoneSpec.onFork ? this.zone : parentDelegate!._forkCurrZone);
  • _hasTaskZS 这里有点不一样了, 有task但没有onScheduleTask,onInvokeTask,onCancelTask的时候,重新指向着三个指针
const zoneSpecHasTask = zoneSpec && zoneSpec.onHasTask;
const parentHasTask = parentDelegate && parentDelegate._hasTaskZS;
if (zoneSpecHasTask || parentHasTask) {
// If we need to report hasTask, than this ZS needs to do ref counting on tasks. In such
// a case all task related interceptors must go through this ZD. We can't short circuit it.
this._hasTaskZS = zoneSpecHasTask ? zoneSpec : DELEGATE_ZS;
this._hasTaskDlgt = parentDelegate;
this._hasTaskDlgtOwner = this;
this._hasTaskCurrZone = zone;
if (!zoneSpec!.onScheduleTask) {
  this._scheduleTaskZS = DELEGATE_ZS;
  this._scheduleTaskDlgt = parentDelegate!;
  this._scheduleTaskCurrZone = this.zone;
}
if (!zoneSpec!.onInvokeTask) {
  this._invokeTaskZS = DELEGATE_ZS;
  this._invokeTaskDlgt = parentDelegate!;
  this._invokeTaskCurrZone = this.zone;
}
if (!zoneSpec!.onCancelTask) {
  this._cancelTaskZS = DELEGATE_ZS;
  this._cancelTaskDlgt = parentDelegate!;
  this._cancelTaskCurrZone = this.zone;
}
}
方法
  • fork
    fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone
    如果找到 this._forkZS,那么调用this._forkZS.onFork,不然返回new Zone

  • intercept
    intercept(targetZone: Zone, callback: Function, source: string): Function
    如果有this._interceptZS, 那么调用this._interceptZS.onIntercept, 不然直接使用callback

  • invoke
    invoke(targetZone: Zone, callback: Function, applyThis: any, applyArgs?: any[],source?: string): any
    如果有this._invokeZS, 那么调用this._invokeZS.onInvoke, 不然直接使用callback

  • handleError
    handleError(targetZone: Zone, error: any): boolean
    有this._handleErrorZS则调用this._handleErrorZS.onHandleError, 不然返回true

  • invokeTask
    invokeTask(targetZone: Zone, task: Task, applyThis: any, applyArgs?: any[]): any
    如果有this._invokeTaskZS, 那么调用this._invokeTaskZS.onInvokeTask, 不然直接使用callback

  • scheduleTask
    scheduleTask(targetZone: Zone, task: Task): Task
    首先使用_scheduleTaskZS.onScheduleTask
    然后task.scheduleFn(task)

  • cancelTask
    cancelTask(targetZone: Zone, task: Task): any
    如果有this._cancelTaskZS则调用,不然调用task.cancelFn。都没有则会报错

  • hasTask
    hasTask(targetZone: Zone, isEmpty: HasTaskState)
    调用this._hasTaskZS.onHasTask, 失败则报错

Task

任务管理

属性
  • type
    包含microTask, macroTask, eventTask

  • TaskState
    包含 notScheduled, scheduling, scheduled, running, canceling, unknown

  • source
    debug使用

  • invoke
    调用函数

  • callback
    调用完成后的回调函数

  • data
    会被传递给scheduleFn

  • runCount
    任务执行的次数,如cancel任务,则会变成-1

方法
  • scheduleFn
    scheduleFn?: (task: Task) => void;
    安排任务

  • cancelFn
    cancelFn?: (task: Task) => void;
    取消以及安排的任务

  • cancelScheduleRequest
    cancelScheduleRequest(): void;
    取消安排任务的请求, 会调用this._transitionTo(notScheduled, scheduling), 如果task不是scheduling则会报错。

只读
  • zone
    会调用callback,需要在创建Task的时候传入
constructor

constructor(type: T, source: string, callback: Function, options: TaskData|undefined, scheduleFn: ((task: Task) => void)|undefined, cancelFn: ((task: Task) => void)|undefined)

辅助方法
  • _transitionTo
    _transitionTo(toState: TaskState, fromState1: TaskState, fromState2?: TaskState)
    Zone的Task在切换状态的时候,会要求输入fromState,如果fromState不是当前状态就会报错,如果 toState是notScheduled,那么this._zoneDelegates会变成null

闭包内全局方法

  • nativeScheduleMicroTask
    function nativeScheduleMicroTask(func: Function)
    nativeScheduleMicroTask入在当前任务线程结束后立刻到用func方法,首先尝试使用的是Promise.resolve().then(), 没有Promise则会使用SetTimout()。
  • scheduleMicroTask
    function scheduleMicroTask(task?: MicroTask)
    首先_numberOfNestedTaskFrames为0和_microTaskQueue队列为空的时候,使用nativeScheduleMicroTask运行drainMicroTaskQueue,最后在_microTaskQueue中添加task

  • drainMicroTaskQueue
    function drainMicroTaskQueue()
    执行_microTaskQueue队列中所有的任务,使用_isDrainingMicrotaskQueue变量保证同一时间,任务只会执行一次。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容