Javascript setTimeout/Promise.resolve:宏任务与微任务

Javascript setTimeout/Promise.resolve:宏任务与微任务,javascript,node.js,event-loop,Javascript,Node.js,Event Loop,我已经了解了微任务和宏任务的概念有一段时间了,从我所读到的所有内容来看,我一直认为设置超时可以被视为创建一个宏任务,而Promise.resolve()(或NodeJS上的进程.nextTick)可以创建微任务 (是的,我知道不同的Promise库(如Q和Bluebird)有不同的Scheduler实现,但这里我指的是每个平台上的本机Promises) 考虑到这一点,我无法解释NodeJS上的以下事件序列(Chrome上的结果与NodeJS(v8lts和v10)不同,并且与我对这个主题的理解相符

我已经了解了微任务和宏任务的概念有一段时间了,从我所读到的所有内容来看,我一直认为
设置超时
可以被视为创建一个宏任务,而
Promise.resolve()
(或NodeJS上的
进程.nextTick
)可以创建微任务

(是的,我知道不同的Promise库(如Q和Bluebird)有不同的Scheduler实现,但这里我指的是每个平台上的本机Promises)

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

for(设i=0;i<2;i++){
设置超时(()=>{
console.log(“超时”,i);
Promise.resolve()然后(()=>{
控制台日志(“承诺1”,i);
}).然后(()=>{
控制台日志(“承诺2”,i);
});
})

}
您无法控制不同的体系结构如何将承诺和超时排队

非常好,请阅读这里:

如果你想要同样的结果,你就必须连锁反应

let chain=Promise.resolve(null)
for(设i=0;i<2;i++){
控制台日志(“链接”,i);
chain=chain.then(()=>Promise.resolve()
.然后(()=>{
设置超时(()=>{
console.log(“超时”,i);
承诺,决心
.然后(()=>{
控制台日志(“承诺1”,i);
})
.然后(()=>{
控制台日志(“承诺2”,i);
})
}, 0)
}))
}

chain.then(()=>console.log('done'))
我不是说我做对了,我专门写了一些东西,我想让你测试一下:

包装器:

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;
}
使用:

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);
}
你甚至可以详细说明一下:

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);
}
你能帮我在你的节点版本中试试吗?在我的节点(6.11,我知道它的旧版本)中,它可以工作

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


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

这被NodeJs团队认为是一个bug,详细信息如下:

同时,它已经被修复并发布了部分节点v11

最好的,
José

FWIW:当我在Node中运行示例几次时,我看到两个结果都会出现几次。这似乎并不完全一致。谢谢你的评论。你说得对,我还没有意识到这一点。这使得实际上更难理解:/尝试大于0的超时?您只是想更好地理解,还是在实际代码中存在实际问题?基本上,您要求的是强制按顺序运行异步代码,使用async/Wait执行此操作非常简单。谢谢您的回答。这是一个简化的例子。我无法在for中更改设置超时。我可以更改的是setTimeout中的Promise.resolve()。问题似乎是setTimeout在两种平台上的运行方式不同。您仍然可以调用setTimeout,只需执行一次。请参阅工作片段。Node和Chrome以不同的方式处理0延迟的循环超时。感谢您的帮助。事实上,问题不在于setTimeout,而在于宏/微任务。我需要的是在宏任务中对一个微任务进行排队,以便在下一个宏任务之前执行该微任务。您知道在NodeJ中安排宏任务的方法吗(我以为setTimeout可以做到这一点,但不幸的是它似乎没有做到这一点)
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