15个例子熟练异步框架 Zone.js

15个例子熟练异步框架 Zone.js

一、理解 Zone.js

可以把 Zone.js 理解成异步的监听器,通过钩子感知异步的各个阶段:

钩子 对应阶段
onScheduleTask 订阅/注册(调用 setTimeout、.then 等)
onInvokeTask 执行(回调真正运行)
onCancelTask 取消(clearTimeout 等)
onHasTask 是否有未完成任务(全部完成时 hasTask 变为 false)

核心价值:

  1. 提取冗余代码:错误处理、耗时统计、日志等可集中到 Zone 钩子,业务回调只保留核心逻辑
  2. 共享变量 :用 properties 在 Zone 上挂数据,Zone.current.get('key') 即可访问,无需闭包或层层传参
  3. 统一错误捕获onHandleError 可捕获 Zone 内同步和异步抛出的错误
  4. 等所有异步完成onHasTask 在 hasTask 变为 false 时,表示全部完成,可触发回调(类似自动版 Promise.all)

官方总结

  • Zone.js 通过包装异步 API,在订阅、执行、取消、完成等阶段提供钩子,让你可以集中处理错误、上下文、监控和"全部完成"等逻辑,从而减少重复代码并提高可读性。

白话文总结:

  • Zone.js 就是一个异步"监听器",可以追踪异步任务的执行取消注册/订阅 等各个阶段,把原本分散在异步代码里的冗余处理(比如日志、错误捕获、耗时统计)提取到统一的位置,让业务代码更清晰。
  • 当"异步套异步"时,各层 Zone 可以共享变量,无需再用闭包或层层传参。
  • 支持在异步任务中统一捕获错误,不用再每处手动 try/catch。
  • 可以检测多个异步任务何时全部完成,比如多个 loading 结束后再统一触发某些操作。

二、示例精华(01-15)

01 最基本用法

javascript 复制代码
// Zone.current 获取当前 Zone
console.log('当前 Zone:', Zone.current.name);

// zone.run() 在 Zone 内执行代码
Zone.current.run(function() {
  console.log('在 Zone 内执行,当前 Zone:', Zone.current.name);
});

讲解 :Zone.js 加载后自动创建 root Zone。zone.run(fn) 在指定 Zone 内执行函数。


02 Zone 嵌套

javascript 复制代码
var childZone = Zone.current.fork({ name: 'child-zone' });
var grandchildZone = childZone.fork({ name: 'grandchild-zone' });

childZone.run(function() {
  console.log('在 child-zone 内:', Zone.current.name);
  grandchildZone.run(function() {
    console.log('在 grandchild-zone 内:', Zone.current.name);
  });
});

讲解zone.fork(config) 基于当前 Zone 创建子 Zone,形成父子层级关系。


03 Zone 存储数据

javascript 复制代码
var myZone = Zone.current.fork({
  name: 'my-zone',
  properties: {
    userId: 'user-123',
    requestId: 'req-456'
  }
});

myZone.run(function() {
  console.log('同步:', Zone.current.get('userId'));
  setTimeout(function() {
    // 异步回调里也能拿到!
    console.log('异步:', Zone.current.get('requestId'));
  }, 500);
});

讲解properties 让 Zone 携带数据,同步和异步代码都能用 Zone.current.get('key') 访问。


04 对比:上下文数据

无 Zone:多层 setTimeout 需闭包或层层传参才能拿到 requestId。

有 Zone:在 Zone 上设置一次,所有异步回调都能直接拿到。

javascript 复制代码
var myZone = Zone.current.fork({
  name: 'request-zone',
  properties: { requestId: 'req-002' }
});

myZone.run(function() {
  setTimeout(function() {
    setTimeout(function() {
      // 照样能拿到,不用传参!
      console.log(Zone.current.get('requestId'));
    }, 200);
  }, 200);
});

05 对比:任务追踪

无 Zone:需手动 pendingCount++/--,每次 setTimeout 前后自己维护。

有 ZoneonHasTask 自动感知「有任务」或「全部完成」。

javascript 复制代码
var trackingZone = Zone.current.fork({
  name: 'tracking-zone',
  onHasTask: function(delegate, current, target, hasTaskState) {
    var hasTask = hasTaskState.macroTask || hasTaskState.microTask || hasTaskState.eventTask;
    // hasTask 为 true:有异步任务
    // hasTask 为 false:全部完成
    console.log(hasTask ? '有任务执行中' : '空闲');
  }
});

trackingZone.run(function() {
  setTimeout(function() {
    setTimeout(function() { /* 什么都不用做 */ }, 300);
  }, 500);
});

06 对比:错误捕获

无 Zone:try-catch 抓不到 setTimeout 里的错误。

有 ZoneonHandleError 统一捕获 Zone 内所有异步错误。

javascript 复制代码
var errorZone = Zone.current.fork({
  name: 'error-zone',
  onHandleError: function(delegate, current, target, error) {
    console.log('捕获到:', error.message);
    return false; // 不继续向外抛
  }
});

errorZone.run(function() {
  setTimeout(function() {
    throw new Error('setTimeout 里的错误!');
  }, 300);
});

07 对比:任务拦截

无 Zone:无法知道 setTimeout、Promise.then 何时执行。

有 ZoneonInvokeTask 在每次异步回调执行前都会触发。

javascript 复制代码
var interceptZone = Zone.current.fork({
  name: 'intercept-zone',
  onInvokeTask: function(delegate, current, target, task, applyThis, applyArgs) {
    console.log('▶ 执行任务:', task.source);
    return delegate.invokeTask(target, task, applyThis, applyArgs);
  }
});

interceptZone.run(function() {
  setTimeout(function() { /* ... */ }, 200);
  Promise.resolve().then(function() { /* ... */ });
});

注意 :onInvokeTask 在回调执行前 触发,不是执行后。delegate.invokeTask() 会同步执行回调,执行完才返回。


08 onScheduleTask vs onInvokeTask

javascript 复制代码
var z = Zone.current.fork({
  name: 'demo',
  onScheduleTask: function(delegate, curr, target, task) {
    console.log('📋 任务被注册:', task.source);  // 调用 setTimeout 的瞬间
    return delegate.scheduleTask(target, task);
  },
  onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
    console.log('▶ 任务即将执行:', task.source);  // 回调真正运行的瞬间
    return delegate.invokeTask(target, task, applyThis, applyArgs);
  }
});

z.run(function() {
  setTimeout(function() { console.log('回调执行了'); }, 500);
});
// 顺序:onScheduleTask → (500ms) → onInvokeTask → 回调

讲解:onScheduleTask = 注册时;onInvokeTask = 执行时。


09 zone.wrap

javascript 复制代码
var myZone = Zone.current.fork({
  name: 'my-zone',
  properties: { requestId: 'req-999' }
});

// 包装后,无论何时何处被调用,都会在 my-zone 内执行
var wrappedCallback = myZone.wrap(function() {
  console.log(Zone.current.get('requestId'));
}, 'button-callback');

document.getElementById('btn').addEventListener('click', wrappedCallback);

讲解:适合 addEventListener、第三方库回调等,你无法控制调用时机,但希望它在你的 Zone 内执行。


10 异步耗时统计

javascript 复制代码
var timingZone = Zone.current.fork({
  name: 'timing-zone',
  onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
    var start = performance.now();
    var result = delegate.invokeTask(target, task, applyThis, applyArgs);
    var cost = (performance.now() - start).toFixed(2);
    console.log(task.source + ' 耗时: ' + cost + ' ms');
    return result;
  }
});

讲解delegate.invokeTask() 是同步的,执行完才返回,所以前后 performance.now() 的差值就是回调耗时。


11 onInvoke 同步钩子

javascript 复制代码
var z = Zone.current.fork({
  name: 'invoke-zone',
  onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs, source) {
    console.log('onInvoke: 即将执行', source);
    return delegate.invoke(target, callback, applyThis, applyArgs, source);
  }
});

z.run(function() {
  console.log('zone.run 里的回调体执行了');
}, null, null, 'main');

讲解onInvoke 针对 zone.run(fn)同步 执行;onInvokeTask 针对异步任务。


12 onCancelTask

javascript 复制代码
var z = Zone.current.fork({
  onCancelTask: function(delegate, curr, target, task) {
    console.log('❌ 任务被取消:', task.source);
    return delegate.cancelTask(target, task);
  }
});

z.run(function() {
  var id = setTimeout(function() {}, 3000);
  // 点击按钮时 clearTimeout(id) → onCancelTask 触发
});

讲解clearTimeoutclearInterval 取消任务时,onCancelTask 会触发。


13 模拟 Angular 变更检测

javascript 复制代码
var ngZone = Zone.current.fork({
  name: 'ng-zone',
  onHasTask: function(delegate, curr, target, hasTaskState) {
    delegate.hasTask(target, hasTaskState);
    var hasTask = hasTaskState.macroTask || hasTaskState.microTask || hasTaskState.eventTask;
    if (!hasTask) {
      console.log('🔔 所有异步完成 → 执行变更检测');
    }
  }
});

ngZone.run(function() {
  setTimeout(function() {
    // 更新数据...
    setTimeout(function() { /* 后续处理 */ }, 200);
  }, 500);
});

讲解:Angular 的 NgZone 就是利用 onHasTask,在「全部完成」时触发变更检测。


14 Zone 边界

javascript 复制代码
// Zone 内发起 → 会被追踪
trackingZone.run(function() {
  setTimeout(function() { /* 会被 onInvokeTask 捕获 */ }, 200);
});

// Zone 外发起 → 不会被追踪
setTimeout(function() { /* 不会被 Zone 追踪! */ }, 400);

讲解 :只有在 Zone 内发起的异步才会被追踪。Zone 外调用的 setTimeout 不会被感知。


15 zone.runGuarded

javascript 复制代码
var safeZone = Zone.current.fork({
  onHandleError: function(delegate, curr, target, error) {
    console.log('捕获:', error.message);
    return false; // 不继续向外抛
  }
});

safeZone.runGuarded(function() {
  throw new Error('故意的错误!');
});
console.log('程序继续运行');

讲解zone.run(fn) 抛错会向外冒泡;zone.runGuarded(fn) 会捕获错误交给 onHandleError,不向外抛。


三、核心概念速查

概念 说明
Zone.current 当前所在的 Zone
zone.run(fn) 在指定 Zone 内执行函数
zone.runGuarded(fn) 安全执行,错误交给 onHandleError
zone.fork(config) 基于当前 Zone 创建子 Zone
zone.wrap(callback) 包装回调,使其在 Zone 内执行
Zone.current.get('key') 获取 Zone 的 properties
properties Zone 携带的数据

四、码云地址

码云地址gitee.com/leeyamaster...

相关推荐
沃尔威武15 小时前
调试黑科技:Chrome DevTools时间旅行调试实战
前端·科技·chrome devtools
yuki_uix15 小时前
虚拟 DOM 与 Diff 算法——React 性能优化的底层逻辑
前端·react.js·面试
yuki_uix15 小时前
从输入 URL 到页面显示——浏览器工作原理全解析
前端·面试
weixin_4080996715 小时前
【完整教程】天诺脚本如何调用 OCR 文字识别 API?自动识别屏幕文字实战(附代码)
前端·人工智能·后端·ocr·api·天诺脚本·自动识别文字脚本
吴声子夜歌15 小时前
ES6——Generator函数详解
前端·javascript·es6
吴声子夜歌15 小时前
ES6——Set和Map详解
前端·javascript·es6
码喽7号16 小时前
vue学习四:Axios网络请求
前端·vue.js·学习
粥里有勺糖16 小时前
视野修炼-技术周刊第129期 | 上一次古法编程是什么时候
前端·javascript·github
whuhewei17 小时前
JS获取CSS动画的旋转角度
前端·javascript·css
蓝黑202017 小时前
Vue组件通信之v-model
前端·javascript·vue