Javascript 如何使用可以启动/停止的递归函数编写应用程序
我有一个应用程序,需要连续运行功能。该函数返回一个承诺。我希望应用程序在再次启动功能之前,等待承诺得到解决 此外,我的应用程序需要一个Javascript 如何使用可以启动/停止的递归函数编写应用程序,javascript,node.js,asynchronous,recursion,promise,Javascript,Node.js,Asynchronous,Recursion,Promise,我有一个应用程序,需要连续运行功能。该函数返回一个承诺。我希望应用程序在再次启动功能之前,等待承诺得到解决 此外,我的应用程序需要一个启动和停止功能,使其分别启动或停止该功能 我这里有一个简化的例子: class App { constructor() { this.running = false this.cycles = 0 } start() { this.running = true this._run() } stop() {
启动
和停止
功能,使其分别启动或停止该功能
我这里有一个简化的例子:
class App {
constructor() {
this.running = false
this.cycles = 0
}
start() {
this.running = true
this._run()
}
stop() {
this.running = false
}
_run() {
Promise.resolve().then(() => {
this.cycles++
}).then(() => {
if (this.running) {
this._run()
}
})
}
}
module.exports = App
我的问题是,当我使用这个时,setTimeout
似乎放弃了我。例如,如果我运行以下命令:
常量应用程序=需要(“./应用程序”)
输出将是:
a.start is not blocking...
然后,setTimeout
回调中的代码永远不会被调用
我可以尝试在命令行上开始运行节点
,并直接在REPL中键入,但在调用a.start()
后,终端冻结,我无法再键入任何内容
这类事情似乎应该是一种相当普遍的模式。例如,Express允许您在没有这些问题的情况下启动/停止服务器。我需要做什么才能获得这种行为?您的\u run()
方法是无限的。除非其他代码可以运行并更改this的值,否则它永远不会停止调用自身。运行但仅使用.then()
不足以可靠地允许其他setTimeout()
代码运行,因为.then()
以高于事件队列中计时器事件的优先级运行
虽然.then()
保证是异步的,但它将以比setTimeout()
更高的优先级运行,这意味着您的递归调用将无限期运行,而您的另一个setTimeout()
将永远无法运行,因此此.running
永远不会更改
相反,如果您使用较短的setTimeout()
本身递归调用\u run()
,那么您的另一个setTimeout()
将有机会运行。而且,由于根本不需要使用承诺,您可以删除它们:
将其更改为:
class App {
constructor() {
this.running = false
this.cycles = 0
}
start() {
this.running = true
this._run()
}
stop() {
this.running = false
}
_run() {
this.cycles++
if (this.running) {
// call recursively after a short timeout to allow other code
// a chance to run
setTimeout(this._run.bind(this), 0);
}
}
}
module.exports = App
有关.then()
、setImmediate()
和nextTick()
之间的相对优先级的讨论,请参见此其他答案:
有关此主题的更多信息,请参阅:
广义优先级层次结构似乎是:
.then()
nextTick()
other events already in the queue
setImmediate()
setTimeout()
因此,您可以从中看到,.then()
跳到队列中已经存在的其他事件之前,因此您的setTimeout()
只要它们是一个.then()
等待执行,就不会运行
因此,如果要在下次调用this.\u run()
之前允许队列中已有的其他计时器事件运行,则必须使用setImmediate()
或setTimeout()
。在这种情况下,这两种方法都可能有效,但由于其他事件是setTimeout()
,我认为在这里使用setTimeout()
可以保证安全,因为您知道新的setTimeout()
回调不能跳到已经是挂起事件的回调之前。您的\u run
方法运行得太快了。它使用承诺并且是异步的,因此不会出现堆栈溢出或其他情况,但是承诺任务队列的优先级高于超时任务队列。这就是您的setTimeout
回调永远不会被触发的原因
如果您使用实际的长时间运行的任务而不是Promise.resolve()
,它将起作用
class App {
constructor() {
this.running = false
this.cycles = 0
}
start() {
if (this.running) {
return Promise.reject(new Error("Already running"))
} else {
this.running = true
return this._run()
}
}
stop() {
this.running = false
}
_run() {
return new Promise(resolve => {
this.cycles++
setTimeout(resolve, 5) // or something
}).then(() => {
if (this.running)
return this._run()
else
return this.cycles
})
}
}
阻止递归Promise
s
这实际上是一个与递归相关的“阻塞”Promise
的好例子
第一次console.log
调用按预期执行。这是一个同步操作,由于Javascript中的调度,它保证在当前的迭代中运行
您的第二个控制台.log
是异步的,由于setTimeout
的实现,它被附加到事件循环的下一次迭代的队列中。但是,由于Promise.resolve()。然后(…)
将then
回调附加到当前迭代的队列末尾,因此永远不会到达下一个迭代。因为这是递归完成的,所以当前迭代永远不会完成
因此,在事件循环的下一轮排队的第二个日志
,从不记录日志
使用node.js,您可以通过使用setImmediate
实现递归函数来重现此行为:
// watch out: this functions is blocking!
function rec(x) {
return x === Infinity
? x
: (console.log(x), setImmediate(rec, x + 1));
}
Bergi的实现通过将setTimeout
应用于resolve回调来绕过正常的异步Promise
行为。这种类型的应用程序称为“服务”。我知道在.Net世界中,您可以创建一个“Windows服务”类型的项目,然后将其注册为任何计算机上的服务,使用控制面板/管理工具/服务停止/启动它。我想你应该调查一下。
// watch out: this functions is blocking!
function rec(x) {
return x === Infinity
? x
: (console.log(x), setImmediate(rec, x + 1));
}