• asap异步执行实现原理


    目录

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

  • 相关阅读:
    USACO Section 1.3 : Mixing Milk
    USACO Section 1.3 : Prime Cryptarithm
    USACO Section 1.2 : Name That Number
    USACO Section 1.3 : Calf Flac
    USACO Section 1.2 : Palindromic Squares
    Global.asax详解
    sql语句字符串处理大全
    ASP.net:URL重写实现IHttpHandler接口
    asp.net中使用基于角色role的Forms验证
    时间Table.TransformColumns(Power Query 之 M 语言)
  • 原文地址:https://www.cnblogs.com/forcheng/p/12701701.html
Copyright © 2020-2023  润新知