web-dev-qa-db-fra.com

setTimeout/Promise.resolve: Macrotask vs Microtask

On m'a initié aux concepts de micro-tâches et de macrotaches depuis un certain temps, et de tout ce que j'ai lu, j'ai toujours pensé quesetTimeoutétait considéré pour créer un macrotask etPromise.resolve()(ou process.nextTick sur NodeJS) pour créer des micro-tâches.

(Oui, je suis conscient que différentes bibliothèques Promise, telles que Q et Bluebird, ont des implémentations de planificateurs différentes, mais je me réfère ici aux promesses natives de chaque plate-forme)

En gardant cela à l'esprit, je suis incapable d'expliquer la séquence d'événements suivante sur NodeJS (les résultats sur Chrome sont différents de NodeJS (les deux versions v8 LTS et v10) et correspondent à ce que j'ai compris à ce sujet). 

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

Ainsi, les résultats que j'ai sur Chrome (et qui concordent avec ma compréhension des tâches micro/macro et du comportement de Promise.resolve et de setTimeout) sont les suivants:

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

Le même code exécuté sur les sorties NodeJS:

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

Je recherche un moyen d'obtenir les mêmes résultats sur NodeJS que sur Chrome. J'ai également testé avec process.nextTick au lieu de Promise.resolve() mais les résultats sont les mêmes.

Quelqu'un peut-il me diriger dans la bonne direction?

12
jpsfs

Cela a été reconnu par l’équipe de NodeJs comme un bogue, plus de détails ici: https://github.com/nodejs/node/issues/22257

En attendant, il était déjà corrigé et publié avec une partie de Node v11.

Best, José

1
jpsfs

Vous ne pouvez pas contrôler la manière dont différentes architectures mettent en file d'attente les promesses et les délais.

Excellente lecture ici: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

Si vous voulez les mêmes résultats, vous devrez enchaîner les promesses.

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'))

1
Steven Spungin

Je ne dis pas que j'ai bien compris, j'ai écrit quelque chose adhoc et j'aimerais que vous testiez les éléments suivants:

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

utiliser:

créer une instance:

var  x = new order;

puis modifiez un peu le reste:

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

J'ai compris:

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

Vous pouvez même élaborer un peu:

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

Dans le noeud v6, vous obtenez:

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

Souhaitez-vous essayer cela dans votre version de noeud pour moi? Dans mon noeud (6.11, je connais son ancien) cela fonctionne.

Testé sur chrome, firefox, node v6.11

Remarque: vous n'êtes pas obligé de conserver la référence à 'x', this dans les fonctions poussées, à l'instance order. Vous pouvez également utiliser Object.defineProperties pour rendre les getters/setters non configurables, pour empêcher la suppression accidentelle de instance.ignited, etc.

0
ibrahim tanyalcin