NodeJS 我无法理解递归中的回调引用[重复]

velaa5lx  于 2023-04-20  发布在  Node.js
关注(0)|答案(2)|浏览(122)

此问题已在此处有答案

How do JavaScript closures work?(86回答)
2天前关闭。
好的,很抱歉代码太长了。这是一个蜘蛛,递归地从网站下载所有的html页面。我的问题是:为什么iterate函数中的回调总是这个函数:

(err) => {
  if (err) {
    console.error(err);
  }

  console.log("All files downloaded");
}

这实际上允许它退出递归。我以为回调会像这样,但当然它会创建一个无限递归:

(err) => {
      if (err) {
        return callback(err);
      }
      iterate(index + 1);
}
const request = require("request");
const fs = require("fs");
const path = require("path");
const cheerio = require("cheerio");

function urlToFilename(url) {
  const parsedUrl = new URL(url);
  const hostname = parsedUrl.hostname;
  const pathname = parsedUrl.pathname;

  return pathname === "/"
    ? `${hostname.slice(0, hostname.lastIndexOf("."))}/${hostname.slice(
        0,
        hostname.lastIndexOf(".")
      )}.html`
    : `${hostname.slice(0, hostname.lastIndexOf("."))}/${pathname.slice(
        0,
        pathname.lastIndexOf(".")
      )}.html`;
}

function getPageLinks(url, body) {
  const $ = cheerio.load(body);
  const linkObjects = $("a");
  const links = [];
  const urlObj = new URL(url);
  linkObjects.each((i, elem) => {
    const link = $(elem).attr("href");

    try {
      const linkObj = new URL(link);
      if (linkObj.hostname === urlObj.hostname) {
        links.push(link);
      }
    } catch (err) {
      // console.error(err);
    }
  });

  return links;
}

function saveFile(filename, contents, callback) {
  fs.mkdir(path.dirname(filename), { recursive: true }, (err) => {
    if (err) {
      return callback(err);
    }

    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename, callback) {
  console.log(`Downloading ${url}`);

  request(url, (err, response, body) => {
    if (err) {
      return callback(err);
    }

    saveFile(filename, body, (err) => {
      if (err) {
        return callback(err);
      }

      console.log(`Downloaded and saved: ${url}`);
      callback(null, body);
    });
  });
}

function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }
  const links = getPageLinks(currentUrl, body);
  function iterate(index) {
    if (index === links.length) {
      return callback();
    }

    spiderNested(links[index], nesting - 1, (err) => {
      if (err) {
        return callback(err);
      }
      iterate(index + 1);
    });
  }
  iterate(0);
}

function spiderNested(url, nesting, callback) {
  const filename = urlToFilename(url);
  fs.readFile(filename, "utf-8", (err, body) => {
    if (err) {
      if (err.code !== "ENOENT") {
        return callback(err);
      }

      return download(url, filename, (err, body) => {
        if (err) {
          return callback(err);
        }

        spiderLinks(url, body, nesting, callback);
      });
    }

    spiderLinks(url, body, nesting, callback);
  });
}

spiderNested(process.argv[2], 1, (err) => {
  if (err) {
    console.error(err);
  }

  console.log("All files downloaded");
});
xu3bshqb

xu3bshqb1#

我仍然认为学习闭包应该足够了,但这里有一个片段显示了iterate()函数的多个变体是如何同时存在的:

function doSomething(callback) {
  function iterate() {
    console.log("This is iterate with callback", callback);
    if (callback) {
      console.log("Calling callback");
      callback();
    }
  }
  console.log("(1) callback is", callback);
  iterate();
  if (!callback) {
    console.log("Recursion here");
    doSomething(iterate);
    console.log("Back from recursion");
  }
  console.log("(2) callback is", callback);
  iterate();
}

doSomething();
.as-console-wrapper {
  max-height: 100% !important
}

输出中最相关的部分可能是这一部分:

This is iterate with callback  function iterate() {
    console.log("This is iterate with callback", callback);
    if (callback) {
      console.log("Calling callback");
      callback();
    }
  }
-----------------------
Calling callback
-----------------------
This is iterate with callback undefined

我们在iterate()内部定义了一个callback,因此调用它,它报告它是iterate(),并使用undefined回调。它来自递归参数。
如果您仔细阅读代码及其输出,我想您也会理解原始代码。

oxalkeyp

oxalkeyp2#

spiderLinks内部,当它调用spiderNested下降到链接到的页面时,就会发生递归:

spiderNested(links[index], nesting - 1, (err) => {
  if (err) {
    return callback(err);
  }
  iterate(index + 1);
});

这是一个不同的回调,所以当处理完该页面时,会调用iterate,这样下一个页面将被类似地分析。
只有递归树的根使用输出完成消息的回调。

相关问题