Skip to content

Asap原理分析 #68

Description

@xiaoxiaosaohuo

asap 原理分析

什么是asap?

asap是as soon as possible的缩写,在Node和浏览器环境下能将回调函数以高优先级任务来执行(下一个事件循环之前)。Promise/A+的异步任务实现就是基于asap这个库。

ASAP会努力的在IO,重排,重绘等事件之前调度任务,也就是常说的会把任务放进微任务队列执行。

usage

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

实现

定义全局变量

// 任务队列
var queue = [];
// 一旦有任务进行就置为true,直到本次队列清空才置为false
var flushing = false;
//requestFlush是一个方法,依据浏览器支持的特性不同会选择不同的API去实现,它会尝试清空任务队列。
var requestFlush;
//标记任务队列中下一个任务的位置
var index = 0;
// 防止内存耗尽,任务队列将定期 //截断已经完成的任务,capacity是任务的最大容量
var capacity = 1024;

flush 清空任务队列

function flush() {
    while (index < queue.length) {
        var currentIndex = index;
        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;
}

可以看到flush 用while循环处理任务队列,直到任务队列为空。任务执行完之后把queue等变量重置。

在flush运行过程中如果有其他地方调用了asap,会将任务加入queue,也就是说queue是动态添加的,但是flush只会运行一次,因为rawAsap保证了requestFlush方法只能调用一次

requestFlush是如何实现的?

requestFlush 是基于MutationObserver实现的,如果浏览器不支持该API,会选择使用定时器实现

var scope = typeof global !== "undefined" ? global : self;
var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
if (typeof BrowserMutationObserver === "function") {
    requestFlush = makeRequestCallFromMutationObserver(flush);
}else{
    requestFlush = makeRequestCallFromTimer(flush);
}

makeRequestCallFromMutationObserver

创建一个空的文本节点,监听其characterData属性的变化,修改node.data属性触发callback

function makeRequestCallFromMutationObserver(callback) {
    var toggle = 1;
    var observer = new BrowserMutationObserver(callback);
    var node = document.createTextNode("");
    observer.observe(node, {characterData: true});
    return function requestCall() {
        toggle = -toggle;
        node.data = toggle;
    };
}

makeRequestCallFromTimer

function makeRequestCallFromTimer(callback) {
    return function requestCall() {
        var timeoutHandle = setTimeout(handleTimer, 0);
        var intervalHandle = setInterval(handleTimer, 50);

        function handleTimer() {
            clearTimeout(timeoutHandle);
            clearInterval(intervalHandle);
            callback();
        }
    };
}

MessageChannel 实现任务调度

通过消息通道也可以实现,但是该Api有些浏览器不支持

function makeRequestCallFromMessageChannel(callback) {
     var channel = new MessageChannel();
     channel.port1.onmessage = callback;
     return function requestCall() {
         channel.port2.postMessage(0);
     };
 }

node环境实现方式一样,只是异步回调使用的是 process.nextTick和setImmediate

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions