我有一个应用程序需要连续运行一个功能。该函数返回一个 promise 。我希望应用程序在再次启动该功能之前等到 promise 得到解决。
此外,我的应用程序需要一个 start
和 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
似乎放弃了我。例如,如果我运行这个:
const App = require("./app")
const a = new App()
a.start()
console.log("a.start is not blocking...")
setTimeout(() => {
console.log("This will never get logged")
a.stop()
console.log(a.cycles)
}, 500)
输出将是:
a.start is not blocking...
然后永远不会调用 setTimeout
回调中的代码。
我可以尝试在我的命令行上开始运行 node
并直接在 REPL 中输入,但是在我调用 a.start()
之后,终端卡住了,我可以不再输入任何内容。
这种东西看起来应该是一个很常见的模式。例如,Express 可以让您启动/停止服务器而不会出现这些问题。我需要做什么才能获得这种行为?
最佳答案
您的_run()
方法是无限的。它永远不会停止调用自身,除非其他代码可以运行并更改 this.running
的值,但仅使用 .then()
不足以可靠地允许您的其他 setTimeout()
代码运行,因为 .then()
以比事件队列中的计时器事件更高的优先级运行。
虽然 .then()
保证是异步的,但它将以比 setTimeout()
更高的优先级运行,这意味着您的递归调用会无限期地运行,而您的其他调用setTimeout()
永远不会运行,因此 this.running
永远不会改变。
相反,如果您使用一个简短的 setTimeout()
本身递归调用 _run()
,那么您的另一个 setTimeout()
将得到一个跑的机会。而且,由于根本不需要在那里使用 promises,您可以删除它们:
改成这样:
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()
之间的相对优先级的一些讨论,请参见其他答案:
Promise.resolve().then vs setImmediate vs nextTick
有关此主题的更多信息:
https://github.com/nodejs/node-v0.x-archive/pull/8325
广义的优先级层次结构似乎是:
.then()
nextTick()
other events already in the queue
setImmediate()
setTimeout()
因此,您可以从中看到 .then()
跳到队列中已有的其他事件前面,因此您的 setTimeout()
永远不会运行只要他们是 .then()
就等着走。
因此,如果您希望在下次调用 this._run()
之前允许队列中的其他计时器事件运行,您必须使用 setImmediate()
或 setTimeout()
。在这种情况下可能两者都有效,但由于其他事件是 setTimeout()
,我想在这里使用 setTimeout()
可以保证安全,因为你知道一个新的 setTimeout()
回调不能跳到已经是挂起事件的前面。
关于javascript - 如何使用可以启动/停止的递归函数编写应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39906021/