tuobaye0711 / tuobaye0711.github.io

拓跋的前端广场
https://tuobaye.com
4 stars 1 forks source link

通过microtasks和macrotasks看JavaScript异步任务执行顺序 | 拓跋的前端客栈 #8

Open tuobaye0711 opened 6 years ago

tuobaye0711 commented 6 years ago

http://tuobaye.com/2017/10/24/%E9%80%9A%E8%BF%87microtasks%E5%92%8Cmacrotasks%E7%9C%8BJavaScript%E5%BC%82%E6%AD%A5%E4%BB%BB%E5%8A%A1%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F/

MarshallShady commented 6 years ago

有一个疑问,为什么不是先打印5呢?因为不管是setTimeout还是promise都属于异步操作,都会进入到任务队列,那么不应该是先执行同步任务打印5,然后先去macrotask中的打印1,在microtask中执行后面的打印呢?

MarshallShady commented 6 years ago

其实我的疑问点在于,promise不是属于microtask的嘛(属于任务队列),为什么会直接进入到promise中执行而不是去执行同步的log(5)呢? 后面总结的一部分是:先从task queue(就是macrotask)中第一个task执行(此时的macrotask不是有一个settimeout嘛,为什么不执行他,而是去执行了microtask中的内容呢?)。 我怎么这么多疑问,我要在仔细看一遍。。

jimczj commented 6 years ago

浏览器的事件循环跟node不一样哦,所以你的结论

JavaScript执行顺序可以简要总结如下: 开始 -> 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 > -> 再次取出microtask全部任务执行 -> … 只适合浏览器,在node就不是这样,欢迎回答问题 https://www.zhihu.com/question/67986024

tuobaye0711 commented 6 years ago

@MarshallShady

其实我的疑问点在于,promise不是属于microtask的嘛(属于任务队列),为什么会直接进入到promise中执行而不是去执行同步的log(5)呢? 后面总结的一部分是:先从task queue(就是macrotask)中第一个task执行(此时的macrotask不是有一个settimeout嘛,为什么不执行他,而是去执行了microtask中的内容呢?)。 我怎么这么多疑问,我要在仔细看一遍。。

是这样的,Promise的目的虽然是实现异步操作,但是promise是分两部分的。。。真正实现异步回调是在then函数里面。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

Promise的执行过程大致是这样的:首先Promise接收一个函数作为参数,而这个函数有两个参数(resolve和reject),我们这里为了简便只传了resolve。然后这个函数是立即执行的(也就是说这个函数里面的内容实际上都是同步的),然后在执行过程中根据fn的执行结果,给Promise切换状态,切换为resolve或者reject,然后根据切换的状态,执行对应的回调。当然这个执行实际上是把then函数的对应部分加入到macrotask中,然后按照队列顺序依次执行~

所以简单点说,Promise依据then函数为界限,前面的可以理解为同步的,后面的可以理解为异步的~

tuobaye0711 commented 6 years ago

@MarshallShady 正好我这篇文章写了个最简单的Promise雏形,其中有段代码:

function Promise(fn) {
    //value用来存储向resolve传递的值,callbacks用来存储已注册的回调函数
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        //将注册的回调函数依次压入栈中
        callbacks.push(onFulfilled);
    };
    function resolve(value) {
        //当调用resolve函数时,根据传入的value,依次执行已注册的回调函数
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }
    fn(resolve);
}

看到最后一行没?fn是立即执行的,所以这个作为Promise参数的函数,并不算异步的。

tuobaye0711 commented 6 years ago

@jimczj 嘿嘿,谢谢回复。 其实关于nodejs和浏览器事件循环的差别,我是知道一些的。只是这篇文章没有涉及到两者区别的部分,所以没有详谈。 就是我这篇文章里面举例的这段代码

setTimeout(function(){
    console.log(1)
},0);
new Promise(function(resolve){
    console.log(2)
    for( var i=100000 ; i>0 ; i-- ){
        i==1 && resolve()
    }
    console.log(3)
}).then(function(){
    console.log(4)
});
console.log(5);

在nodejs中的运行结果是跟浏览器一模一样的哦~ nodejs跟浏览器在事件循环上机制的主要区别我总结了一下主要是以下两点:

  1. nodejs中多了几个新异步处理函数,比如说process.nextTick、setImmediate等
  2. Promise内部实现机制

具体还有其他什么区别我就不太了解了。。。但是可以肯定的是,在这篇文章里我讲的这个及其简单的例子,和例子的执行顺序,在浏览器和nodejs环境里面没有什么差别~

如果我哪里说错了,欢迎给我指出来哟~

jimczj commented 6 years ago

@tuobaye0711 我一直觉得,把任务分为两个队列,是会对人造成误解。其实macrotask,和microtask只是任务种类划分,不能算队列。 1.取task第一个task执行,这里怎么取其实是有规则,不是谁先来就取谁,不同任务源就算你先到了,没执行到你的队列,你就晚执行。 2.取microtask全部任务依次执行,也不是先来先执行,比如process.nextTick,总是比promise.then快执行 关于event loop 其实很多人了解一些,但是又了解不全,我也是,一直想总结,但是总是会有些疑问没解开

tuobaye0711 commented 6 years ago

@jimczj

取microtask全部任务依次执行,也不是先来先执行,比如process.nextTick,总是比promise.then快执行

你讲这个我也意识到了,我总结的这个顺序严格来说是针对浏览器的eventloop机制的。因为我主要参考的文章是Tasks, microtasks, queues and schedules,作者Jack Archibald是chrome的开发人员,他的教程针对的应该是浏览器的event loop,所以是有局限性的。

process.nextTick和promise.then同属microtask的,但是实际执行顺序并不是先来后道的。就我个人的理解,process.nextTick方法可以在当前执行栈的尾部,下一次Event Loop之前触发回调函数。也就是说,它指定的任务总是发生在本个Event loop结尾,所有其他异步任务之前。这也就是很多知乎大佬总结出process.nextTick > promise.then > setTimeout的原因了~

刚刚又翻到那篇文章里的一段回复, nexttick 作者说了,nextTick只是更接近microtasks......