为什么要使用process.nextTick来保证异步任务的正确执行?
Why use process.nextTick to ensure correct execution of asynchronous tasks?
我正在按照一本书 (Node.js design patterns) 的示例在 Node.js 和 Javascript 中实现 LIMITED PARALLEL EXECUTION 算法。
首先,我编写了一个 TaskQueue class 来处理任务的所有限制和执行。
class TaskQueue {
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
runTask(task) {
return new Promise((resolve, reject) => {
this.queue.push(() => {
return task().then(resolve, reject);
});
process.nextTick(this.next.bind(this));
});
}
next() {
while (this.running < this.concurrency && this.queue.length) {
const task = this.queue.shift();
task().finally(() => {
this.running--;
this.next();
});
this.running++;
}
}
}
然后我写了一个函数,returns 一个在随机数毫秒后解析的承诺,并用分配的数字记录承诺。
此函数将任务委托给队列,将承诺包装在将由 TaskQueue 按需执行的函数中 class。
function promiseResolveRandomValue(promiseNumber, queue) {
return queue.runTask(() => {
return new Promise(resolve => {
const random = Math.round(Math.random()*1000);
setTimeout(() => {
console.log('Resolved promise number: ', promiseNumber, ' with delay of: ', random);
resolve();
}, random);
});
})
}
最后,我使用了一个 map 在十个数字的数组上执行函数,这将帮助我们跟踪每个承诺。
const queue = new TaskQueue(2);
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const promises = array.map((number) => {
return promiseResolveRandomValue(number, queue);
});
Promise.all(promises)
.then(() => console.log('Finished'));
到目前为止一切顺利。问题是作者建议用 process.nextTick 执行这行代码以避免任何 Zalgo 类型的情况,但我不明白为什么,因为这段代码执行完全相同只是调用 next 的方法class.
runTask(task) {
return new Promise((resolve, reject) => {
this.queue.push(() => {
return task().then(resolve, reject);
});
process.nextTick(this.next.bind(this)); // <-------------------------------- WHY?
});
}
有人能解释一下为什么在某些情况下使用 process.nextTick 可以方便地避免与同步和异步代码发生冲突吗?
作者所指的 Zalgo situation 是
console.log("Before scheduling the task")
queue.runTask(() => {
console.log("The task actually starts");
return new Promise(resolve => {
// doesn't really matter:
setTimeout(resolve, Math.random()*1000);
});
});
console.log("Scheduled the task");
如果您没有使用 nextTick
,日志可能会以不同的顺序出现 - 任务实际上可能在 runTask()
函数 returns 之前开始,具体取决于那里有多少已经在队列中了。
这可能并不那么出乎意料 - 毕竟,我们期望 runTask
到 运行 任务 - 但是,如果任务实际上同步开始并带有一些副作用,那可能是出乎意料的。这种副作用的一个例子是 next
方法本身 - 考虑以下内容:
queue.runTask(() => …);
console.log(`Scheduled the task, there are now ${queue.queue.length} tasks in the queue`);
您是否希望长度始终至少为 1?避免 Zalgo 可以提供这样的保证。
(另一方面,如果我们改为记录 queue.running
,期望它在调用 runTask
后立即 >=1 对我来说似乎同样明智,并且需要开始 next
同步。清晰的文档在这里胜过任何直觉。)
我正在按照一本书 (Node.js design patterns) 的示例在 Node.js 和 Javascript 中实现 LIMITED PARALLEL EXECUTION 算法。
首先,我编写了一个 TaskQueue class 来处理任务的所有限制和执行。
class TaskQueue {
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
runTask(task) {
return new Promise((resolve, reject) => {
this.queue.push(() => {
return task().then(resolve, reject);
});
process.nextTick(this.next.bind(this));
});
}
next() {
while (this.running < this.concurrency && this.queue.length) {
const task = this.queue.shift();
task().finally(() => {
this.running--;
this.next();
});
this.running++;
}
}
}
然后我写了一个函数,returns 一个在随机数毫秒后解析的承诺,并用分配的数字记录承诺。
此函数将任务委托给队列,将承诺包装在将由 TaskQueue 按需执行的函数中 class。
function promiseResolveRandomValue(promiseNumber, queue) {
return queue.runTask(() => {
return new Promise(resolve => {
const random = Math.round(Math.random()*1000);
setTimeout(() => {
console.log('Resolved promise number: ', promiseNumber, ' with delay of: ', random);
resolve();
}, random);
});
})
}
最后,我使用了一个 map 在十个数字的数组上执行函数,这将帮助我们跟踪每个承诺。
const queue = new TaskQueue(2);
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const promises = array.map((number) => {
return promiseResolveRandomValue(number, queue);
});
Promise.all(promises)
.then(() => console.log('Finished'));
到目前为止一切顺利。问题是作者建议用 process.nextTick 执行这行代码以避免任何 Zalgo 类型的情况,但我不明白为什么,因为这段代码执行完全相同只是调用 next 的方法class.
runTask(task) {
return new Promise((resolve, reject) => {
this.queue.push(() => {
return task().then(resolve, reject);
});
process.nextTick(this.next.bind(this)); // <-------------------------------- WHY?
});
}
有人能解释一下为什么在某些情况下使用 process.nextTick 可以方便地避免与同步和异步代码发生冲突吗?
作者所指的 Zalgo situation 是
console.log("Before scheduling the task")
queue.runTask(() => {
console.log("The task actually starts");
return new Promise(resolve => {
// doesn't really matter:
setTimeout(resolve, Math.random()*1000);
});
});
console.log("Scheduled the task");
如果您没有使用 nextTick
,日志可能会以不同的顺序出现 - 任务实际上可能在 runTask()
函数 returns 之前开始,具体取决于那里有多少已经在队列中了。
这可能并不那么出乎意料 - 毕竟,我们期望 runTask
到 运行 任务 - 但是,如果任务实际上同步开始并带有一些副作用,那可能是出乎意料的。这种副作用的一个例子是 next
方法本身 - 考虑以下内容:
queue.runTask(() => …);
console.log(`Scheduled the task, there are now ${queue.queue.length} tasks in the queue`);
您是否希望长度始终至少为 1?避免 Zalgo 可以提供这样的保证。
(另一方面,如果我们改为记录 queue.running
,期望它在调用 runTask
后立即 >=1 对我来说似乎同样明智,并且需要开始 next
同步。清晰的文档在这里胜过任何直觉。)