【问题标题】:Iterating keys of global object attempts to access undefined variables in asychronous callbacks迭代全局对象的键尝试在异步回调中访问未定义的变量
【发布时间】:2017-07-15 14:47:21
【问题描述】:
if (Object.keys(globalObject).length != 0) {
  Object.keys(globalObject).forEach(function(key) {
    database.query('SELECT * from accounts WHERE `ID` = ' + key + ' LIMIT 1', function(error, rows, fields) {
      if (rows.length == 0) return;
      console.log('User has  ' + globalObject[key].length + ' items')
      globalObject[key].forEach(function(item) {
        console.log('Item ID is ' + item.id)
      });
      delete globalObject[key]
    })
  })
}

globalObject 示例(根据连接的用户和每个用户项不断变化):

globalObject = { "1001":[{id:1},{id:2}], "1002":[{id:2},{id:3}]}

有时我会遇到致命错误,崩溃:

TypeError: Cannot read property 'length' of undefined

在这一行:

console.log('User has  ' + globalObject[key].length + ' items)

尽管在任何时候从程序的不同部分推送不同的用户,我删除 globalObject[key] 的唯一地方是在我处理完用户之后。

那么当我通过一个迭代器 (forEach) 访问它时,为什么一个键不能存在(未定义),它确保该键存在并且直到最后才会被删除?

编辑:

我认为原因是我在 setInterval(每 200 毫秒)内调用了 forEach,因此在 forEach 完成之前再次调用它,因此删除了密钥。我怎样才能使它更加同步以避免在很短的时间内为相同的键调用两次?

【问题讨论】:

  • 如果一个对象没有键(一个空对象),它显然会给你一个错误。
  • 您当前问题之外的一个小改进:从let keys = Object.keys(globalObject); if (keys.length > 0) { keys.forEach(...) ...开始。
  • 虽然这里的每个人都在疯狂追逐,但由于不是minimal reproducible example,我投票决定将此作为题外话结束。您有一个带有全局变量的程序,您承认在此函数运行和每个database.query() 回调之间会对其进行更新。如果没有进一步的信息,就不可能确切地告诉您是什么实际上导致了问题,我们只能猜测。请edit您的问题以尽可能少的代码重现此问题。
  • 您是在回复电子邮件还是什么?我在发布后立即删除了该评论,因为我发现为时已晚。
  • 您可能希望在代码中添加一个简单的控制台日志,以确保您了解正在发生的事情。除了将keys 捕获为自己的列表之外,还可以设置keys.forEach(key => { let data = globalObject[key]; console.log(processing 键${key} 和关联数据${JSON.stringify(data)});,这样您就可以看到您的代码将要尝试什么使用,然后在您的数据库回调中有一个类似的日志,以查看是否存在数据不匹配。打赌你的“全局对象”永远不应该是全局的,因为在你的查询运行时其他代码可能会弄乱它。

标签: javascript arrays node.js


【解决方案1】:

最有可能的情况是,您一次运行多个循环,这会产生潜在的竞争条件(由于异步数据库调用),其中一个循环删除了另一个循环仍在处理的键.

最好的解决办法是避免同时运行其中两个,同时防止可能的竞争条件。如果您向我们展示调用代码上下文,以便我们了解如何/是否可以在另一个完成之前多次调用它,我们可以建议避免这种情况的方法。

即使没有,您也可以像这样防止竞争条件:

  Object.keys(globalObject).forEach(function(key) {
    database.query('SELECT * from accounts WHERE `ID` = ' + key + ' LIMIT 1', function(error, rows, fields) {
      // if the db says no rows or the key is already gone, then nothing to do
      if (rows.length == 0 || !globalObject[key]) return;
      console.log('User has  ' + globalObject[key].length + ' items')
      globalObject[key].forEach(function(item) {
        console.log('Item ID is ' + item.id)
      });
      delete globalObject[key];
    });
  });

添加到if 语句的额外!globalObject[key] 检查可防止其他人在您的数据库调用期间已删除密钥的情况。


顺便说一句,你不需要这个if:

if (Object.keys(globalObject).length != 0) { 

因为如果数组为空,Object.keys(globalObject).forEach() 工作正常(它只是无事可做)。


根据您的 cmets 进行更多解释:

.forEach() 循环运行完成而不会被中断。但是循环中的数据库操作是非阻塞的。他们开始了,但循环不会等待他们完成。因此,.forEach() 循环将完成,但循环内的回调尚未被调用。

因此,如果您有 10 个键,您将启动 10 个数据库操作(还没有完成)。现在,.forEach() 循环完成了。如果您的 setInterval() 在所有 10 个数据库操作完成之前再次调用此代码,那么一个新的 .forEach() 循环将运行并启动更多的数据库操作,基于即将被第一个循环删除的键(当它的数据库操作终于完成)。现在,在第二个 .forEach() 循环运行后,第一个循环中的数据库操作开始完成并删除键。然后,在它们完成后,来自您的第二个 .forEach() 循环的数据库操作完成并调用它们的回调,并且它们正在尝试使用现在已被第一个循环中的回调删除的键。

循环本身不会同时运行。但是,在每个循环运行之后,它会设置在未来某个不确定时间运行的数据库回调,并且这些回调(由您编码)假设一组特定的键没有被删除。但是,如果您没有等到第一个循环中的所有数据库回调都完成后才开始第二个循环,那么该假设就会失效,因为第二个循环将使用第一个循环的数据库调用将在它们删除时删除的键终于完成了。这会弄乱由第二个循环启动的数据库回调。

.forEach() 调用与事件无关。它们只是 Javascript 中的循环,它们同步运行。数据库调用只是启动异步操作。他们的回调将由未来某个时间的事件触发。因为这些数据库调用是非阻塞异步的,.forEach() 循环结束,然后一段时间后 JS 解释器获取触发数据库回调被调用的事件。

【讨论】:

  • 谢谢。我从stackoverflow.com/questions/44728125/… 想起了你。呵呵。事件一次运行一个。 foreach 循环不应该是一个事件吗?我在这里,有两个 foreach 循环相互混淆。我找到了另一种方法来做我想做的事,这次没有间隔,但我仍然感觉我并不真正知道什么是异步,什么不是XD。
  • @needitohelp - forEach 循环不是事件。它与事件无关。这只是 Javascript 中的一个循环。 forEach 循环运行所有迭代,启动所有数据库操作,然后在 forEach 循环完成后,数据库操作一一完成并调用它们的回调。
  • 如果第二个 foreach 循环可以同时运行,而第一个循环尚未完成,则您在该线程中的确认“没有两个函数可以同时运行。”为假,因为 forEach 循环是一个函数。
  • @needitohelp - 我在答案的末尾添加了更多内容,以尝试解释 .forEach() 循环和异步数据库调用之间的交互。您的问题是数据库调用是非阻塞的,因此 .forEach() 循环完成(不,您不会同时运行两个实际循环),但其中的数据库调用没有尚未被调用。它们是事件驱动的,并且会在数据库完成工作后运行一段时间。
  • 谢谢。我知道每个 forEach 都保证一次运行一个,但 forEach 本身的“内容”不是(取决于你在里面做什么)。现在我知道为什么存在异步模块了。如果我理解了更大的图景,那么现实是,当我从 foreach 内部调用异步函数时,我就失去了 foreach 的同步特性。所以我想成为这方面大师的秘诀是学会快速识别什么是同步的,什么不是。这需要一段时间 XD。
猜你喜欢
  • 1970-01-01
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-22
  • 1970-01-01
相关资源
最近更新 更多