admin管理员组

文章数量:1327675

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);
		});
	})
}

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?

Share Improve this question edited Aug 10, 2018 at 21:24 jpsfs asked Aug 10, 2018 at 20:51 jpsfsjpsfs 7082 gold badges7 silver badges24 bronze badges 13
  • FWIW: When I run the example in Node a few times I see both results happen a few times. It doesn't seem to be pletely consistent. – Alexander O'Mara Commented Aug 10, 2018 at 20:55
  • Thanks for you ment. You are correct, I had not realized that yet. That makes this even more hard to understand actually :/ – jpsfs Commented Aug 10, 2018 at 20:58
  • Try a timeout greater than 0? – Bergi Commented Aug 10, 2018 at 20:58
  • Are you just trying to get a better understanding or do you have an actual problem in real code? – Bergi Commented Aug 10, 2018 at 20:59
  • 1 Basically what you are asking for is async code to be forced to run in order, pretty simple to do this using async / await. – James Commented Aug 10, 2018 at 23:15
 |  Show 8 more ments

3 Answers 3

Reset to default 2

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

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

Best, José

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

Excellent Read Here: https://jakearchibald./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'))

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.

本文标签: javascriptsetTimeoutPromiseresolve Macrotask vs MicrotaskStack Overflow