简体   繁体   中英

setTimeout / Promise.resolve: Macrotask vs Microtask

I've been introduced to the concepts of Microtasks and Macrotasks for a while now, and from everything I've read, I always thought setTimeout to be considered to create a macrotask and Promise.resolve() (or process.nextTick on NodeJS) to create microtasks.

(Yes, I'm aware that different Promise libraries like Q and Bluebird have different schedulers implementations, but here I'm referring to the native Promises on each platform)

With this in mind I'm unable to explain the following sequence of events on NodeJS (results on Chrome are different from NodeJS (both v8 LTS and v10) and match with my understanding on this subject).

 for (let i = 0; i < 2; i++) { setTimeout(() => { console.log("Timeout ", i); Promise.resolve().then(() => { console.log("Promise 1 ", i); }).then(() => { console.log("Promise 2 ", i); }); }) } 

So, the results I have on Chrome (and that are consistent with my understanding of Micro/Macro tasks and how Promise.resolve and setTimeout behave) are:

Timeout  0
Promise 1  0
Promise 2  0
Timeout  1
Promise 1  1
Promise 2  1

The same code executed on NodeJS outputs:

Timeout  0
Timeout  1
Promise 1  0
Promise 2  0
Promise 1  1
Promise 2  1

I'm looking for a way to have the same results on NodeJS that I have on Chrome. I've also tested with process.nextTick instead of Promise.resolve() but the results are the same.

Can anyone point me into the right direction?

You can't control how different architectures queue the promises and timeouts.

Excellent Read Here: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

If you want the same results you are going to have to chain promises.

 let chain = Promise.resolve(null) for (let i = 0; i < 2; i++) { console.log("Chaining ", i); chain = chain.then(() => Promise.resolve() .then(() => { setTimeout(() => { console.log("Timeout ", i); Promise.resolve() .then(() => { console.log("Promise 1 ", i); }) .then(() => { console.log("Promise 2 ", i); }) }, 0) })) } chain.then(() => console.log('done')) 

This was recognized by the NodeJs team as a bug, more details here: https://github.com/nodejs/node/issues/22257

Meantime it was already fixed and released has part of Node v11.

Best, José

I am not saying I got it right, I wrote something adhoc and I'd like you to test the below:

the wrapper:

function order(){
    this.tasks = [];
    this.done = false;
    this.currentIndex = 0;
    this.ignited = false;
}
order.prototype.push = function(f){
    var that =  this,
        args = Array.prototype.slice.call(arguments).slice(1);
    if(this._currentCaller){
        this.tasks.splice(
            this.tasks.indexOf(this._currentCaller) + 1 + (this.currentIndex++),
            0,
            function(){that._currentCaller = f; f.apply(this,args);}
        );
    } else {
        this.tasks.push(function(){that._currentCaller = f; f.apply(this,args);});
    }
    !this.ignited && (this.ignited = true) && this.ignite();
    return this;
}
order.prototype.ignite = function(){
    var that = this;
    setTimeout(function(){
        if(that.tasks.length){
            that.tasks[0]();
            that.tasks.shift();
            that.repeat(function(){that.reset(); that.ignite()});
        } else {
            that.ignited = false;
            that.reset();
        }
    },0);
}
order.prototype.repeat = function(f){
    var that = this;
    if(this.done || !this.tasks.length){
        f();
    } else {
        setTimeout(function(){that.repeat(f);},0);
    }
}
order.prototype.reset = function(){
    this.currentIndex = 0; 
    delete this._currentCaller; 
    this.done = false;
}

to use:

create an instance:

var  x = new order;

then modify the rest a bit:

for (let i = 0; i < 2; i++) {
    x.push(function(i){
        setTimeout(() => {
            console.log("Timeout ", i);
            x.push(function(i){
                Promise.resolve().then(() => {
                    console.log("Promise 1 ", i);
                }).then(() => {
                    console.log("Promise 2 ", i);
                    x.done = true;
                })
            },i);
            x.done = true;
        });
    },i);
}

I get this:

Timeout  0
Promise 1  0
Promise 2  0
Timeout  1
Promise 1  1
Promise 2  1

You can even elaborate a bit:

for (let i = 0; i < 2; i++) {
    x.push(function(i){
        setTimeout(() => {
            console.log("Timeout ", i);
            x.push(function(i){
                Promise.resolve().then(() => {
                    console.log("Promise 1 ", i);
                }).then(() => {
                    console.log("Promise 2 ", i);
                    x.done = true;
                })
            },i)
            .push(function(i){
                Promise.resolve().then(() => {
                    console.log("Promise 1 ", i);
                }).then(() => {
                    console.log("Promise 2 ", i);
                    x.done = true;
                })
            },i+0.5)
            .push(function(i){
                Promise.resolve().then(() => {
                    console.log("Promise 1 ", i);
                }).then(() => {
                    console.log("Promise 2 ", i);
                    x.done = true;
                })
            },i+0.75);
            x.done = true;
        });
    },i);
}

In node v6, you get:

Timeout  0
Promise 1  0
Promise 2  0
Promise 1  0.5
Promise 2  0.5
Promise 1  0.75
Promise 2  0.75
Timeout  1
Promise 1  1
Promise 2  1
Promise 1  1.5
Promise 2  1.5
Promise 1  1.75
Promise 2  1.75

Would you try this in your node version for me? In my node (6.11, I know its old) it works.

Tested on chrome, firefox, node v6.11

Note: you don't have to keep reference to 'x', this within the pushed functions refer to the order instance. You can also use Object.defineProperties to render getters/setters unconfigurable, to prevent accidental deletion of instance.ignited etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM