是否会以递归方式调用异步函数来填充堆栈?

时间:2016-12-12 04:03:19

标签: node.js asynchronous recursion

我有以下功能。对不起,它有点长......但它并不是真的很重要。 它是一个简单的函数,它将遍历消息,并通过websockets发送它们。

最关键的部分,以及这个问题的症结在于:这一行是否会导致堆栈填满?

// through this loop
sendMessagesInTab( tabId, cb );

...?基本上,到周期结束时,可能会在列表中添加更多记录。由于此函数只运行一次(那里有一个信号量,currentlyDeliveringTab[ tabId ]),当它完成时我想再次检查列表中没有添加任何内容。要做到这一点,我使用函数本身。

从我的测试来看,我还没有真正设法填满筹码。我认为这是因为这不是递归'因为一切都是异步的。但我感到困惑:如果事情被添加到所有,这会占用所有可用内存吗?

function sendMessagesInTab( tabId, cb ){

  consolelog("Entered sendMessagesInTab for tab", tabId);

  // Semaphore. Only one instance of this is to run at any given time
  if( currentlyDeliveringTab[ tabId ] ){
    consolelog("Already running for tab ", tabId);
    return;
  }
  currentlyDeliveringTab[ tabId ] = true;

  consolelog("Looking up tab...");
  stores.tabs.dbLayer.selectById( tabId, function( err, tab ){
    if( err ){
      delete currentlyDeliveringTab[ tabId ];
      return cb( err );
    }
    if( ! tab ){
      delete currentlyDeliveringTab[ tabId ];
      return new Error("tabId not found!");
    }


    stores.tabMessages.dbLayer.selectByHash( { tabId: tabId }, function( err, tabMessages ){
      if( err ){
        delete currentlyDeliveringTab[ tabId ];
        return cb( err );
      }


      if( !tabMessages.length ){
        consolelog("No messages to be delivered, that's it...");
        delete currentlyDeliveringTab[ tabId ];
        return cb( null );
      }


      /*
      // TESTING RECURSION
      delete currentlyDeliveringTab[ tabId ];
      return sendMessagesInTab( tabId, function(err ) { console.log("ERROR:", err ) } );
      */

      consolelog("There are messages to be delivered:", tabId, tabMessages.length );
      async.eachSeries(

        tabMessages,

        function( record, cb ){

          consolelog("Checking the connection...");

          // If the connection is not there, all good but "false" (delivery failed)
          var ws = connections[ record.tabId ] && connections[ record.tabId ].ws;
          if( ! ws ) return cb( new Error("No websocket connection") ); // End of cycle will kill currentlyDeliveringTab

          var message = record.message;
          message.messageId = record.id;

          // Attempt delivery over websocket. If it works, great. If it doesn't,
          // sorry.
          consolelog("Attempt to stringify the message", tabId);
          try {
            var strMessage = JSON.stringify( message );
          } catch ( err ){
            return cb( err );
          }

          consolelog("Sending message through the websocket", tabId);
          ws.send( strMessage, function( err ){
            if( err ) return cb( err ); // End of cycle will kill currentlyDeliveringTab

            consolelog("Deleting the message", tabId);

            stores.tabMessages.dbLayer.deleteById( record.id, function( err ){
              if( err ) return cb( err ); // End of cycle will kill currentlyDeliveringTab

              consolelog("Updating lastSync", tabId);

              cb( null );
            });
          })
        },

        function( err ){
          if( err ){
            consolelog("ERROR!", err );
            delete currentlyDeliveringTab[ tabId ];
            return cb( err );
          }

          consolelog("All messages have been sent successfully!");
          consolelog("Now running sendMessagesInTab again in case messages were added WHILE sending these");
          delete currentlyDeliveringTab[ tabId ];

          // Rerun sendMessagesInTab to check that messages weren't added while going
          // through this loop
          sendMessagesInTab( tabId, cb );
        }
      );

    });
  });
}

1 个答案:

答案 0 :(得分:2)

  

会以递归方式调用异步函数来填充堆栈吗?

不,如果从异步回调调用它,它不会导致任何堆栈累积。当您执行异步函数然后将控制权返回给系统时(等待调用回调),当前执行线程已经完成,因此堆栈被清空,然后在完成事件时启动一个全新的堆栈触发异步操作,然后触发完成回调。

请记住,与C / C ++这样的语言相比,Javascript中的一个不同之处是函数作用域和函数作用域中的对象是垃圾收集实体而不在堆栈中。因此,即使执行线程已完全展开并清除堆栈,您也可以拥有一个围绕异步回调的闭包,该回调仍处于活动状态。当任何活动代码无法访问函数作用域中的元素时,它们将被垃圾回收(类似于Javascript中的其他对象)。因此,它仍然存在,而异步回调仍然可以发生,但一旦回调发生并且不再再发生,那么范围内的所有内容都有资格进行垃圾回收。

但是,只要调用异步回调并且回调完成执行,就应该对该局部范围进行GCed。除非您继续添加越来越多的活动处理程序并且可以被调用,否则获取本地作用域的构建将是一种不寻常的情况。如果无法再调用事件处理程序(因为启动它们的内部对象本身已完成),则作用域中的项目无法访问,并且符合GC的条件。