setTimeout / Promise.resolve:宏任务与微任务
setTimeout / Promise.resolve: Macrotask vs Microtask
我接触微任务和宏任务的概念已经有一段时间了,从我读过的所有内容来看,我一直认为 setTimeout
是考虑创建宏任务和 Promise.resolve()
(或 process.nextTick
在 NodeJS 上)创建微任务。
(是的,我知道 Q 和 Bluebird 等不同的 Promise 库有不同的调度器实现,但这里我指的是每个平台上的原生 Promises)
考虑到这一点,我无法解释 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 上得到的结果(这与我对 Micro/Macro 任务的理解以及 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()
进行了测试,但结果是一样的。
谁能指出我正确的方向?
您无法控制不同架构如何对承诺和超时进行排队。
优秀阅读此处: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'))
我并不是说我做对了,我写了一些临时的东西,我希望你测试下面的内容:
包装器:
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
使 getters/setters 不可配置,以防止意外删除 instance.ignited
等
这已被 NodeJs 团队识别为一个错误,更多详细信息请参见:https://github.com/nodejs/node/issues/22257
同时它已经修复并发布了 Node v11 的一部分。
最好,
何塞
我接触微任务和宏任务的概念已经有一段时间了,从我读过的所有内容来看,我一直认为 setTimeout
是考虑创建宏任务和 Promise.resolve()
(或 process.nextTick
在 NodeJS 上)创建微任务。
(是的,我知道 Q 和 Bluebird 等不同的 Promise 库有不同的调度器实现,但这里我指的是每个平台上的原生 Promises)
考虑到这一点,我无法解释 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 上得到的结果(这与我对 Micro/Macro 任务的理解以及 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()
进行了测试,但结果是一样的。
谁能指出我正确的方向?
您无法控制不同架构如何对承诺和超时进行排队。
优秀阅读此处: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'))
我并不是说我做对了,我写了一些临时的东西,我希望你测试下面的内容:
包装器:
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
使 getters/setters 不可配置,以防止意外删除 instance.ignited
等
这已被 NodeJs 团队识别为一个错误,更多详细信息请参见:https://github.com/nodejs/node/issues/22257
同时它已经修复并发布了 Node v11 的一部分。
最好, 何塞