在while循环中的异步函数

6
我有一个关于如何在while循环中执行异步任务直到满足某些条件的问题。这更多是一个理论性问题,但我可以看出在某些情况下这可能是一个问题。
我将尝试通过示例说明问题(我在这里使用JavaScript,但您可以使用任何语言):
我可能有一个设备,我想在该设备达到特定状态之前保持我的应用程序。如果我可以使用同步方法来获取设备状态,则代码可能如下所示:
// Hold until the desired state is reached
var state = false;
while (!state) {
    state = device.getStateSync();
}
// [...] continue the program

我的问题是:当我从设备得到一个异步的getState函数时,我应该如何转换这段代码?代码应该在调用的执行时间不确定的情况下正常工作,并且应该考虑到我正在使用有限的内存和堆栈大小。

// [...] This would call the async function in a loop and crash the program
while (!state) {
    // [...] something
    device.getStateAsync(function(newState) {
        state = newState;
    });
    // [...] something else
}

我找到了一篇有递归解决方案的帖子(http://blog.victorquinn.com/javascript-promise-while-loop)。虽然这是一个不错的解决方案,但如果循环调用太频繁,它最终会遇到堆栈大小问题。
目前我有一种直觉,即可能没有解决方案。你知道有什么方法可以做到这一点吗?或者你知道如何证明没有办法做到吗?请随意包括更复杂的概念,例如线程、Promise或Future。
请记住,这是一个理论问题,示例是为了说明在我无法更改正在使用的框架(或设备)的情况下。
感谢每一个回应和想法!
Pedro

不要使用 while 循环。只需让回调函数调用 getStateAsync 直到 state 不允许为止。完全没有调用堆栈大小问题。 - cookie monster
我不明白为什么你认为链接的代码会有堆栈大小问题。它使用了一个Promise库并进行异步调用。 - cookie monster
5个回答

4
在Javascript中,除非实际改变条件的代码在循环内部或者在循环中调用的某个函数的副作用里面,否则你不能循环等待条件的改变。这是因为Javascript是单线程的(除了WebWorkers不考虑在内),所以只要循环在Javascript中循环,其他代码就无法运行,因此其他代码永远无法更改您的条件变量。你将会得到一个无限循环,因为循环等待永远不会改变的东西。最终,浏览器会抱怨您有不响应的代码正在运行并关闭它。
因此,在javascript中没有不确定或长时间的等待循环。虽然可能会循环一秒钟左右,让一些时间过去,但这很少有用、有生产力或编写JS代码的最佳方法。
相反,您必须通过触发事件或回调来通知感兴趣的代码条件已经改变,而感兴趣的代码可以订阅该事件或注册其回调。或者,您必须定时轮询来查看条件是否已更改(第一种选项是首选)。
如果你正在设计一个API,其中你想能够允许一些调用代码知道状态何时改变,通常你会实现回调或Promise。回调方法如下:
device.registerStateChangeCallback("type of state interested in", fn);

然后,API将在指定状态更改为新值时调用传入的回调函数。API可以决定这是一次性通知还是每次状态更改直到取消注册回调函数。

因此,调用者不必在忙碌的循环中等待状态更改,而是编写异步代码(这就是JavaScript处理此类问题的方式),并使用稍后将在状态更改时调用的回调函数。例如,调用者的代码可能如下所示:

device.registerStateChangeCallback("syncState", function(newState) {
     // caller puts code here that wants to do something when 
     // the syncState has changed
});

如果通知只是一次性的,那么您也可以使用 promises。这时API仅返回一个promise,当syncState改变时,该promise就被解决:

device.registerStateChange("syncState").then(function(newState) {
     // caller puts code here that wants to do something when 
     // the syncState has changed
});

Promise的缺点是它们只能单次使用(仅一次通知),因此如果您需要多个通知,则最好使用回调函数。与回调函数相比,Promise的优点在于它们提供了许多功能来将它们与其他事件同步(例如排序,等待一系列事件全部完成,协调多个异步事务等),并且它们提供更好的异步错误处理。


2

您不能这样做。原因是Javascript是单线程的。在while循环运行时,其他任何代码都不会被执行,因此期望更新设备状态的异步任务将永远没有机会运行。Javascript中的异步任务由主事件处理程序循环执行。这意味着异步操作只有在从脚本返回到事件处理程序或脚本通过调用事件处理程序(当脚本使用confirmprompt等方法等待用户输入时)时才会运行。


0

我知道这有点晚了,但是es6提供了更好的方法来解决这个问题。 解决方案在于使用生成器+承诺

看一下这个存储库,在这里我必须创建Twitter线程,其中前面的推文必须回复到以前的推文,并且使用当前推文的ID来获取下一个推文。

请看这里 => https://github.com/Udokah/tweet-threader


0

这确实是一个非常理论性的问题。使用异步 API 获取当前状态可能会很奇怪。你可能会有以下两种情况:

  • 同步方法可以给你当前的状态;
  • 或者异步方法在状态改变时会回调你。在这种情况下,无需循环,只需等待回调即可。

但如果你确实有这样的 API 的具体示例,请告诉我们...


同意。到目前为止,我还没有遇到过会出现这个问题的API。而且我希望永远不会^^ 当我试图弄清楚同步和异步方法是否具有相同的功能时,我遇到了这个问题。 - torpedro

0

此函数使用setTimeout,因此回调函数在不进行递归的情况下返回。要求是您的环境实现setTimeout机制,因为这不是JavaScript的一部分。

var callback = function callback(result){
   if(result == "some condition"){
       //done
   } else {
       setTimeout(device.getStateAsync(callback), 10);
   }
}

device.getStateAsync(callback);

arg += 1 不会更新 i,它只会更新局部变量 arg - Barmar
你的代码只检查一次状态,它不会持续检查直到状态改变。 - Barmar
我假设 getStateAsync 会向回调函数提供多个回调。 - Charles Smartt
除非getStateAsync将其注册为事件处理程序,每当状态更改时都会调用它,如@jfriend00的答案所建议的那样。 - Barmar
@Barmar 我已经改用了设置超时机制的方法。我以前也用过这种方法,而且取得了相当大的成功。 - Charles Smartt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接