【问题标题】:setTimeout / Promise.resolve: Macrotask vs MicrotasksetTimeout / Promise.resolve:宏任务与微任务
【发布时间】:2019-01-18 12:08:48
【问题描述】:

我接触微任务和宏任务的概念已经有一段时间了,从我所阅读的所有内容来看,我一直认为 setTimeout 可以考虑创建一个宏任务和 Promise.resolve()(或 NodeJS 上的process.nextTick)来创建微任务。

(是的,我知道 Q 和 Bluebird 等不同的 Promise 库有不同的调度程序实现,但这里我指的是每个平台上的原生 Promise)

考虑到这一点,我无法解释 NodeJS 上的以下事件序列(Chrome 上的结果与 NodeJS(v8 LTS 和 v10)不同,并且与我对这个主题的理解相匹配)。

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

所以,我在 Chrome 上得到的结果(这与我对微/宏任务以及 Promise.resolve 和 setTimeout 的行为方式的理解一致)是:

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

在 NodeJS 输出上执行的相同代码:

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

我正在寻找一种在 NodeJS 上获得与在 Chrome 上相同的结果的方法。我也用process.nextTick而不是Promise.resolve()进行了测试,但结果是一样的。

谁能指出我正确的方向?

【问题讨论】:

  • FWIW:当我在 Node 中运行该示例几次时,我看到这两个结果都发生了几次。它似乎并不完全一致。
  • 感谢您的评论。你是对的,我还没有意识到这一点。这使得这实际上更加难以理解:/
  • 试试大于 0 的超时时间?
  • 您是想更好地理解还是在实际代码中遇到实际问题?
  • 基本上你要求的是异步代码被强制按顺序运行,使用 async / await 很简单。

标签: javascript node.js event-loop


【解决方案1】:

NodeJs 团队将其识别为一个错误,更多详细信息请参见:https://github.com/nodejs/node/issues/22257

同时它已经修复并发布了 Node v11 的一部分。

最好, 何塞

【讨论】:

    【解决方案2】:

    您无法控制不同架构如何对承诺和超时进行排队。

    在这里阅读:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

    如果你想要相同的结果,你将不得不链接承诺。

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

    【讨论】:

    • 感谢您的回答。这是一个简化的例子。我无法更改 for 中的 setTimeout。我可以改变的是 setTimeout 中的 Promise.resolve()。
    • 似乎问题在于 setTimeout 在两个平台上的运行方式不同。
    • 你仍然可以调用 setTimeout,只需执行一次。
    • 请参阅工作 sn-p。 Node 和 Chrome 以不同的方式处理循环超时,延迟为 0。
    • 感谢您的帮助。事实上,问题不在于 setTimeout,而在于 Macro/Microtasks。我需要的是在宏任务中排队一个微任务,以便在以下宏任务之前执行微任务。你知道在 NodeJS 中调度 MacroTask 的方法吗(我认为 setTimeout 会这样做,但不幸的是它似乎不会这样做)
    【解决方案3】:

    我并不是说我做对了,我写了一些特别的东西,我希望你测试以下内容:

    包装器:

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

    使用:

    创建一个实例:

    var  x = new order;
    

    然后稍微修改其余部分:

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

    我明白了:

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

    你甚至可以详细说明一下:

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

    在节点 v6 中,您会得到:

    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
    

    你会在你的节点版本中为我试试这个吗?在我的节点(6.11,我知道它是旧的)中它可以工作。

    在 chrome、firefox、node v6.11 上测试

    注意:您不必在推送函数中保留对“x”的引用,this 引用 order 实例。您还可以使用Object.defineProperties 使getter/setter 不可配置,以防止意外删除instance.ignited 等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-28
      • 1970-01-01
      • 2019-07-12
      • 2022-11-27
      • 1970-01-01
      • 2014-11-12
      • 2018-07-01
      相关资源
      最近更新 更多