目录

  • 为什么分析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. D2T1服务器需求——毒?瘤题(并不是

    这题我第一眼居然差点错了\(OTZ\) 然后写了线段树,还写挂了-- 写好了\(query\)操作,发现似乎不需要区间查询,然后又删掉-- 看着这熟悉的操作,似乎在哪里见过-- 然后我莫名其妙把一个\ ...

  2. javascript中indexOf()和lastIndexOf()详解

    ECMAScript5为数组实例添加两方法:indexOf()和lastIndexOf().这两个方法接受两个参数:要查找的项和(可选的)表示查找起点位置的索引.其中,indexOf()方法从数组的开 ...

  3. [前端开发]Vue组件化的思想

    组件化的思想 将一个页面中的处理逻辑放在一起,处理起来就会很复杂,不利于后续管理和扩展. 如果将页面拆分成一个个小的功能块,每个功能块实现自己的内容,之后对页面的管理和维护就变得很容易了. 注册组件的 ...

  4. 【简说Python WEB】flask-mail电子邮件异步Asynchronous

    系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:virutalenv Python的版本:Python 3.6.9 flask-mail电子邮件异步Asynchronou ...

  5. jenkins-gitlab-harbor-ceph基于Kubernetes的CI/CD运用(一)

    注:这部分的学习还是要靠自己多点点 多尝试尝试 这部分19年3月份我是玩的很溜的,一年没用,基本忘光光了. 学习要温故而知新! 流程拓扑图 前提准备 部署应用服务 部署kubernetes 集群:ht ...

  6. Andorid 添加MenuPopup

  7. MS15-034漏洞复现、HTTP.SYS远程代码执行漏洞

    #每次命令都百度去找命令,直接弄到博客方便些: 漏洞描述: 在2015年4月安全补丁日,微软发布的众多安全更新中,修复了HTTP.sys中一处允许远程执行代码漏洞,编号为:CVE-2015-1635( ...

  8. HDOJ 1301最小生成树的Kruskal算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1301 将结点的字符信息处理成点信息即可,代码如下: #include<bits/stdc++.h ...

  9. [JVM教程与调优] 了解JVM 堆内存溢出以及非堆内存溢出

    在上一章中我们介绍了JVM运行时参数以及jstat指令相关内容:[JVM教程与调优] 什么是JVM运行时参数?.下面我们来介绍一下jmap+MAT内存溢出. 首先我们来介绍一下下JVM的内存结构. J ...

  10. 密钥对格式转换:JKS到PEM

    此处脚本用途:Tomcat的JKS转换成Nginx的PEM格式. #!/bin/bash export JKS=$1 export PASS=$2 NAME=$(basename "$JKS ...