了解节点/ JavaScript回调函数

时间:2015-11-18 00:27:11

标签: node.js callback

我理解回调函数背后的概念如下:作为参数传递给另一个函数的函数。其背后的想法是,当事件“A”发生时,函数“A”可以使用函数“B”,但在此之前代码仍然可以正常运行,而不是等待事件“A”。我不明白的是一些语法以及代码实际如何使其工作。

所以使用这样的代码:

var http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8080);

console.log('Server running on port 8080.');

我理解函数(req,res)部分是执行回调函数的“匿名函数”。但我不知道如何以及为什么。为什么关键字要执行此“功能”,这些参数来自哪里?我仍然没有找到一个很好的解释这是如何工作的。我在高度抽象的隐喻层面得到了它的工作原理,但我不明白代码的含义。

3 个答案:

答案 0 :(得分:0)

首先,让我们取出匿名函数并使其成为常规函数:

var http = require('http');

function serverCallback(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}

http.createServer(serverCallback).listen(8080);

在这里你可以看到代码正在调用http.createServer()并传递一个参数,这是一个函数引用。

根据http.createServer()的文档,当它调用该回调时,它会将两个参数传递给该函数。第一个参数是request对象,第二个参数是response对象。因此,当我们声明我们的回调函数时,我们声明并将这两个参数命名为回调,以便我们可以正确使用发送给它的参数。

与Javascript回调混淆的一个共同点是,http.createServer()函数决定传递此回调的参数,我们必须声明我们的回调参数以匹配主机函数将传递给它的内容。这些参数名称只是方便的编程名称,我们可以用来引用这两个参数 - 我们称之为不会影响实际传递的内容。因此,我们必须选择与http.createServer()函数实际传递给回调的名称相匹配的名称。

所以,既然您已经了解了基本回调的工作原理,那么命名函数serverCallback可以像这样内联:

var http = require('http');

http.createServer(function serverCallback(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(8080);

我实际上只是采用了serverCallback的最高定义并将其置于内联中。注意,它甚至还有函数名称。这也可以正常工作。但是,我们并没有真正使用serverCallback名称,因此不需要它。这可以删除,最终得到这个:

var http = require('http');

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(8080);

现在这是一个内联匿名函数声明。它与常规函数声明相同,它只是声明为内联,函数名称是可选的。

现在,针对您的具体问题:

  

据我所知,函数(req,res)部分是“匿名的”   函数“这是执行回调函数的原因。但是我   不知道怎么做和为什么。为什么关键字要执行此“功能”,以及   这些参数来自哪里?

function是你如何声明一个函数。这是内联函数声明,因此仍然需要function关键字。参数在我上面的解释中描述。它们被声明为我们在代码中使用的方便的标记,以匹配http.createServer()在调用它时传递回调的内容。 req是一个请求对象,res是一个响应对象,并且在node.js文档中都有自己的页面,因为它们具有属性和方法。

  

我仍然没有找到一个很好的解释这是如何工作的。我在高度抽象的隐喻层面得到了它的工作原理,但我不明白代码的含义。

如果我的上述说明为您提供有关代码如何运作的任何进一步问题,请告诉我。

  

我不明白的是一些语法和代码如何   实际上是让它发挥作用。

如果,您在这里问的是,每当某个服务器事件发生时,Javascript如何调用此回调,那么这是一个更长的对话。 http.createServer()后面是一些设置服务器的本机代码。在TCP级别,它定义正在侦听新连接的传入端口。当其中一个新连接发生时(来自外部的某个人建立与您的服务器的连接),OS服务将通知此http服务器后面的本机代码已建立新的传入连接(例如,现在已连接新的TCP套接字并且已通过该套接字发送HTTP请求)。然后,此后面的本机代码将向Javascript事件队列添加事件和一些数据。

如果当前的JS解释器当前没有运行,那么向事件队列添加内容将触发该事件的回调,它将创建适当的参数并调用关联的回调,执行您注册为回调的任何代码。如果JS引擎当前正在运行其他一些JS代码,那么该事件将位于JS事件队列中,直到当前的JS线程完成执行。此时,JS引擎将把下一个事件从事件队列中拉出并执行它(调用它的回调)。

有关事件队列的一般概念的更多信息,您可以阅读以下其他帖子:

How does JavaScript handle AJAX responses in the background?

Where is the node.js event queue?

答案 1 :(得分:0)

reqres参数(请求和响应)在节点HTTP Server module.中定义

让我们简化一下并制作我们自己的回调函数:(不是匿名函数,所以应该更容易理解)

function first(callback) {
    console.log("First!");
    callback();
}
function second() {
    console.log("Second!");
}
first(second);

应输出到控制台:首先!第二!

答案 2 :(得分:0)

JavaScript中的函数是一个Object。因此,您可以这样对待并传递它。除非你告诉它,它不会立即执行。除非你告诉它,否则它根本不会执行。

所以,如果我有一个抓狗玩具的功能:

function fetchDogToy() {
   trackToy();
   runTowardsToy();
   pickUpToy();
}

我有一个让我回到玩具的功能:

function returnToyToOwner() {
  runTowardsOwner();
  dropToy();
}

returnToyToOwner完成之前,我可能不希望pickUpToy执行。因此,我会将returnToyToOwner传递给fetchDogToy,因此我可以指定 我想要执行它。在我能做到这一点之前,我需要指定fetchDogToy能够接受一个参数,但我们知道这将是一个函数对象,所以只需相应地命名:

function fetchDogToy(callbackFunction) {
   trackToy();
   runTowardsToy();
   pickUpToy();
}

现在,我们可以传入一个参数。我们知道这将是一个函数对象,因此,让我们在它们希望它执行时告诉它:

function fetchDogToy(callbackFunction) {
   trackToy();
   runTowardsToy();
   pickUpToy();
   callbackFunction(); // executed the function object after toy is picked up!
}

所以,这是fetchDogToy的设置。我们可以称之为两种不同的方式。一种方法是在致电returnToyToOwner时直接传递fetchDogToy

fetchDogToy(returnToyToOwner);

但是,在整个应用程序中不同情况下玩具被拾取后,您可能需要fetchDogToy的不同行为,因此我们通常只是按原样编写原始函数对象并以这种方式传递:

fetchDogToy(function returnToyToOwner() {
      runTowardsOwner();
      dropToy();
  });

它可以命名为function,或匿名:

fetchDogToy(function() {
      runTowardsOwner();
      dropToy();
  });

希望这有帮助!

旁注:暂时考虑fetchDogToy是您在某些API在线文档中阅读的第三方库函数。您现在可以看到,如果他们希望您将函数对象作为回调传递,那么您真的不知道何时他们将调用您传递的代码,除非您自己阅读源代码。