目录

  • 为什么分析asap
  • asap概述
  • asap源码解析—Node版
  • 参考

1.为什么分析asap

在之前的文章 async和await是如何实现异步编程? 中的 “浅谈Promise如何实现异步执行” 小节,提到了 Promise 异步执行是通过 asap 这个库来实现的。所以为了进一步深入 Promise 异步执行的原理,深入分析一下 asap 是有必要的。

补充说明:这里提及的Promise并不是Node和浏览器的原生实现,是一个第三方库实现,仅以此为参考。

2.asap概述

asap 是 as soon as possible 的简称,在 Node 和浏览器环境下,能将回调函数以高优先级任务来执行(下一个事件循环之前),即把任务放在微任务队列中执行。

宏任务(macro-task)和微任务(micro-task)表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

用法:

asap(function () {
// ...
});

3.asap源码解析—Node版

asap 源码库中包含了支持Node和浏览器的两个版本,这里主要进行分析Node版。

主要包含两个源码文件:

这两个文件分别导出了 asap 和 rawAsap 这两个方法,而 asap 可以看作是对 rawAsap 的进一步封装,通过缓存的 domain(可以捕捉处理 try catch 无法捕捉的异常,针对异步代码的异常处理)和 try/finally 实现了即使某个任务抛出异常也可以恢复任务栈的继续执行,另外也做了一点缓存优化(具体见源码)。

因此这里主要分析 raw.js 里面的代码即可:

1.首先是对外导出的 rawAsap 方法

var queue = [];
var flushing = false; function rawAsap(task) {
if (!queue.length) {
requestFlush();
flushing = true;
}
queue[queue.length] = task;
}

源码解析:如果任务栈 queue 为空,则触发 requestFlush 方法,并将 flushing 标志为 true,并且始终会将要执行的 task 添加到任务栈 queue 的末尾。这里需要注意的是由于 requestFlush 中是异步去触发任务栈的执行的,所以即使queue[queue.length] = task在 requestFlush 调用之后执行,也能保证在任务栈 queue 真正执行前,任务 task 已经被添加到了任务栈 queue 的末尾。(如果任务栈 queue 不为空,说明 requestFlush 已经触发了,此时任务栈正在被循环依次执行,执行完毕会清空任务栈)

2.其次是异步触发 flush 方法执行的 requestFlush 方法

var domain;
var hasSetImmediate = typeof setImmediate === "function"; // 设置为 rawAsap 的属性,方便在任务执行异常时再次触发 requestFlush
rawAsap.requestFlush = requestFlush;
function requestFlush() {
// 确保 flushing 未绑定到任何域
var parentDomain = process.domain;
if (parentDomain) {
if (!domain) {
// 惰性加载执行 domain 模块
domain = require("domain");
}
domain.active = process.domain = null;
} if (flushing && hasSetImmediate) {
setImmediate(flush);
} else {
process.nextTick(flush);
} if (parentDomain) {
domain.active = process.domain = parentDomain;
}
}

源码解析:核心代码其实就一句:setImmediate(flush),通过 setImmediate 异步执行 flush 方法。而判断 parentDomain 以及设置和恢复 domain 都只是为了当前的 flush 方法不绑定任何域执行。而这里还有一个 hasSetImmediate 判断,是为了做兼容降级处理,如果不存在 setImmediate 方法,则使用 process.nextTick 方法触发异步执行。但使用 process.nextTick 方法有一个缺陷,就是它不能够处理递归。

3.最后是执行任务栈的 flush 方法

// 下一个任务在任务队列中执行的位置
var index = 0;
var capacity = 1024; function flush() {
while (index < queue.length) {
var currentIndex = index;
// 在调用任务之前先设置下一个任务的索引,可以确保再次触发 flush 方法时,跳过异常任务
index = index + 1;
queue[currentIndex].call(); // 防止内存泄露
if (index > capacity) {
for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
queue[scan] = queue[scan + index];
}
queue.length -= index;
index = 0;
}
}
queue.length = 0;
index = 0;
flushing = false;
}

源码解析:通过 while 循环依次去执行任务栈 queue 中的每一个任务,这里需要注意一点,index + 1 表示下一个要执行的任务下标,而其放在 queue[currentIndex].call() 之前,是为了保证当当前任务执行发生异常了,再次触发 requestFlush 方法时,能够跳过发生异常的任务,从下一个任务开始执行。而判断 if (index > capacity) 是为了防止内存泄露,当任务栈 queue 的长度超过了指定的阈值 capacity 时,对任务栈 queue 中的任务进行移动,将所有剩余的未执行的任务置前,并重置任务栈 queue 的长度。当所有任务执行完毕后,重置任务栈以及相应状态。

4.总结

rawAsap 方法是通过 setImmediate 或 process.nextTick 来实现异步执行的任务栈,而 asap 方法是对 rawAsap 方法的进一步封装,通过缓存的 domain 和 try/finally 实现了即使某个任务抛出异常也可以恢复任务栈的继续执行(再次调用rawAsap.requestFlush)。

4.参考

【翻译】Promises/A+规范

asap - High-priority task queue for Node.js and browsers

最新文章

  1. F#之旅7 - 图片处理入门
  2. nanoTime对volatile 测试的一种写法
  3. (转)关于Certificate、Provisioning Profile、App ID的介绍及其之间的关系
  4. Oracle数据库初级学习 2
  5. sqlsever备份,还原和导入导出方法
  6. Spring AOP使用整理:使用@AspectJ风格的切面声明
  7. 1996: [Hnoi2010]chorus 合唱队 - BZOJ
  8. HDU 4628
  9. Linux下glui 的安装,以及错误解决
  10. mybati之day02
  11. perl 爬虫两个技巧
  12. Linux X86在下面TLB机制
  13. linux反弹shell
  14. ASP.NET MVC5+EF6+EasyUI 后台管理系统(87)-MVC Excel导入和导出
  15. [整理]k-vim-for-server通过vimrc修改vim格式
  16. JVM的Server与Client运行模式区别与切换
  17. 110道python面试笔试题汇总,你能答对几道?
  18. 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情(一)转载自码农网
  19. CSS hack 360浏览器 极速模式与兼容模式
  20. socket.io笔记一

热门文章

  1. Spark入门(七)--Spark的intersection、subtract、union和distinc
  2. 手动生成WebService代理类
  3. 035.集群安全-Pod安全
  4. markdown样式测试
  5. Java并发编程(03):多线程并发访问,同步控制
  6. 拜托,别再问我什么是 B+ 树了
  7. Springboot CORS跨域访问
  8. PMP备考日记(一)
  9. TensorFlow系列专题(三):深度学习简介
  10. adb 之日志文件分析(五)