目录

  • 为什么分析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

asap异步执行实现原理的更多相关文章

  1. Async和Await异步编程的原理

    1. 简介 从4.0版本开始.NET引入并行编程库,用户能够通过这个库快捷的开发并行计算和并行任务处理的程序.在4.5版本中.NET又引入了Async和Await两个新的关键字,在语言层面对并行编程给 ...

  2. Atitit.异步编程技术原理与实践attilax总结

    Atitit.异步编程技术原理与实践attilax总结 1. 俩种实现模式 类库方式,以及语言方式,java futuretask ,c# await1 2. 事件(中断)机制1 3. Await 模 ...

  3. Python开发程序:RPC异步执行命令(RabbitMQ双向通信)

    RPC异步执行命令 需求: 利用RibbitMQ进行数据交互 可以对多台服务器进行操作 执行命令后不等待命令的执行结果,而是直接让输入下一条命令,结果出来后自动打印 实现异步操作 不懂rpc的请移步h ...

  4. Spring源码学习之:@async 方法上添加该注解实现异步调用的原理

    在我们使用spring框架的过程中,在很多时候我们会使用@async注解来异步执行某一些方法,提高系统的执行效率.今天我们来探讨下 spring 是如何完成这个功能的.    spring 在扫描be ...

  5. Vue(基础七)_webpack(webpack异步加载原理)

    ---恢复内容开始--- 一.前言 1.webpack异步加载原理’                                           2.webpack.ensure原理     ...

  6. php异步执行其他程序

    这里的“其他程序”,可能是linux命令,可能是其他的php文件. 网上说法有四种.分别为: 1.通过加载页面的时候通过ajax技术异步请求服务器 2.通过popen()函数 3.通过curl扩展 4 ...

  7. C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较

    使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...

  8. php 异步执行脚本

    这里说的异步执行是让php脚本在后台挂起一个执行具体操作的脚本,主脚本退出后,挂起的脚本还能继续执行.比如执行某些耗时操作或可以并行执行的操作,可以采用php异步执行的方式.主脚本和子脚本的通讯可以采 ...

  9. PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)

    源码地址:https://github.com/Tinywan/PHP_Experience 测试环境配置: 环境:Windows 7系统 .PHP7.0.Apache服务器 PHP框架:ThinkP ...

随机推荐

  1. textareaCenter 未完结 其实就是iview的textarea的从写一遍 需求是光标上下居中

    重点1: 一但赋值内容,光标会失去,导致光标到第一位 解决方法 设置一个状态位isChange,编辑的时候不进行watch更新,因为emit会自动改变外层的值,触发watch 解决方法2 找回上一次的 ...

  2. 【vue生命周期】- 详解

    这篇文章通俗易懂,写的不错,本文转载至:https://www.cnblogs.com/happ0/p/8075562.html 详解Vue Lifecycle 先来看看vue官网对vue生命周期的介 ...

  3. ASP.NET WebApi实现Token验证

    记录笔记,在博客园中有很多实现Token的方法,这是我看过他们学到的,然后找到适合自己的解决方案,自己无聊总结一下学习经验写下的 WebApi后端接口实现Token验证 Token是在客户端频繁向服务 ...

  4. 最详细的 Spring Boot 多模块开发与排坑指南

    创建项目 创建一个 SpringBoot 项目非常的简单,简单到这里根本不用再提.你可以在使用 IDEA 新建项目时直接选择 Spring Initlalize 创建一个 Spring Boot 项目 ...

  5. 【攻防世界】simple-unpack

    知识:upx脱壳 simple-unpack 难度系数: 3.0 题目来源: 暂无 题目描述:菜鸡拿到了一个被加壳的二进制文件 提示说有壳子:然后用PE分析发现是ELF upx的壳子

  6. 原创 记录一次线上Mysql慢查询问题排查过程

    背景 前段时间收到运维反馈,线上Mysql数据库凌晨时候出现慢查询的报警,并把原始sql发了过来: --去除了业务含义的sql update test_user set a=1 where id=1; ...

  7. nginx 内存池

    参考 https://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html?tdsourcetag=s_pctim_aiomsg 源码版本 n ...

  8. 最便捷的神经网络可视化工具之一--Flashtorch

    前言 几周前,我在AnitaB.org组织的Hopperx1 London上发表了演讲作为伦敦科技周的一部分. 在演讲结束后,我收到了热烈的反馈,所以我决定写一个稍微长一点的演讲版本来介绍FlashT ...

  9. Windows系统向Ubuntu传输文件

    PuTTY传输: 安装PuTTY,然后将PuTTY安装目录下的pscp.exe文件拷贝到/Windows/System32/目录下,在cmd控制台执行命令: # pscp 要传输的文件路径 ubunt ...

  10. 曹工说Redis源码(1)-- redis debug环境搭建,使用clion,达到和调试java一样的效果

    概要 最近写了spring系列,这个系列还在进行中,然后有些同学开始叫我大神,然后以为我各方面都比较厉害,当然了,我是有自知之明的,大佬大神什么的,当作一个称呼就好,如果真的以为自己就是大神,那可能就 ...