如果回调调用封闭函数,它是否被称为递归调用

If a callback calls the enclosing function, would it be called a recursive call?

本文关键字:调用 是否 被称为 递归 函数 回调 如果      更新时间:2023-09-26

例如:

如果http.request调用是在res.on('end')回调中进行的,那么调用是递归的吗?

http.request(options, function(res) {
  res.on('data', function(chunk) {
    data+=chunk;
  });
  res.on('end', function(){
    //do some stuff
    http.request(options, function(res) {...});//is this recursive?
  });
}).end();

编辑:

让我们举一个更简单的例子:假设有一个函数逐个字符读取文件:

var noOfChar = 10;
var i = 0;
readChar(function processChar(char){
  if(i < noOfChar) {
    console.log(char);
    i++;
    readChar(processChar); //is this recursive?
  }
}

与其争论要标记什么,不如让我们考虑一下代码的一些具体属性,以及它与人们通常所说的"递归"有何相似或不同。

通常,递归函数是在每一步都增加堆栈的东西(尾部调用递归除外,它使用了一个技巧来防止这种增长)。但在 node 中,异步回调会丢弃外部上下文的堆栈。(尝试在回调中引发异常,然后自己看看。因此,此代码不会增加堆栈。

通常递归函数是调用自己的函数,但我在您的示例中没有看到这种情况发生。http上的两个侦听器是不同的功能。

第二个示例不直接调用自己,但它确实间接调用自己。你有一个"基本情况"(noOfChar >= 10),此时递归展开。如果readChar同步的,您甚至会增加堆栈。所以这似乎更接近递归。

请注意,在第二个示例中,您有一个命名函数,而第一个示例只有匿名函数。一般来说,我认为如果没有命名函数(或至少是一些保存函数的变量)就不可能递归,因为否则函数如何引用自身?

如果您要向它传递不同的callback,我认为它不会是递归的。

如果您将相同的callback传递给两者,它将是递归的。

递归意味着如果我在函数定义本身中调用相同的函数。但是,如果您要传递不同的回调,它将类似于波纹管。

假设您有 2 个不同的回调callback1 and callback2

您已将第一个回调传递给外部http.request,将第二个回调传递给内部http.request,因此外部http.request将执行callback1中给出的代码,但内部将执行callback2中给出的代码。

我不明白这个问题的含义。
如果答案是肯定的,你会被老板鞭打致死吗?
有没有反对递归的法律?

http.request只是达到目的的手段。无论如何,您需要多次调用它来处理事务。
示例代码的问题在于,您在一大块代码中处理异步交换,该代码直接处理响应而不跟踪事务的状态。

我想到的唯一评论是,这种逻辑非常脆弱,因为终止条件取决于传递给您的请求的任何参数,从而为一系列潜在问题敞开了大门,例如不同步的答案和无限循环(如果它们最终挂起系统,它们是否值得"递归调用"的名称就没什么意义了)。

我宁愿建议构建一个显式状态机来跟踪预期的HTTP交换的进度,这将免除有关递归性和相关"良好实践"的哲学问题。

我将

尝试回答我自己的问题,因为我认为现在我对递归的理解更好了。也许这会帮助其他人。

我的怀疑是,如果一个函数调用一个回调,再次调用该函数,它会被称为递归。我觉得这类似于间接递归,其中函数 1 调用函数 2,函数 2 再次调用函数 1。因此,如果函数 1 调用函数 2 而函数 3 又调用函数 1 并不重要,它仍然是递归的。同样,函数调用一个回调,调用另一个调用函数的回调也无关紧要。如果第二个示例是递归的,则第一个示例也可以是递归的。

问题的

递归解决方案必须更多地与问题的解决方式有关,而不是像堆栈增长这样的语言实现。考虑tail-recursion,它不会增加调用堆栈,但没有人质疑它的递归性。

所以关键点是递归思考。以整个问题为例,如果其中一部分可以用与原始问题相同的方式解决,那么解决方案就是递归的。调用自身的函数可能不符合递归的条件。有些人可能称其为自引用函数,但不是递归函数。

递归至少应该由一个基本情况和一个递归情况组成,每个自引用都应该使输入值更接近基本情况,这将结束递归。所以我认为问题中提到的两个例子都不是递归的

递归调用可能不会导致调用堆栈增加。有尾部调用递归,它保持调用堆栈大小恒定。

以以下由回调组成的示例为例,此处外部函数堆栈帧可能在内存中,也可能不在内存中。

var input = [[1,2,3],[4,5,6],[7,8,9]];
function double(ip) { 
  return ip.map(function (element) {
    if(Array.isArray(element)) {
      return double(element);
    }
    else {
      return 2 * element;
    }
  });
}
console.log(double(input));

我会称之为递归。我们也可以做匿名递归,所以显式命名函数是不必要的。