简体   繁体   English

Node.js在回调内部调用回调函数

[英]Node.js calling a callback function inside a callback

I started learning node.js by reading Node JS in Action book recently. 我最近通过阅读《行动》中的Node JS开始学习node.js。 This is probably a newbie question, but after reading several posts of callback functions and javascript scope of variables, I still have problem understand the idea behind this code in chapter 5 of the book. 这可能是一个新手问题,但是在阅读了回调函数和变量的javascript作用域的几篇文章之后,在本书第5章中理解此代码背后的思想仍然有困难。

function loadOrInitializeTaskArray(file, cb) {
  fs.exists(file, function(exists) {
    var tasks = [];
    if (exists) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err) throw err;
        var data = data.toString();
        var tasks = JSON.parse(data || '[]');
        cb(tasks);
      });
    } else {
      cb([]); 
    }
  });
}

function listTasks(file) {
  loadOrInitializeTaskArray(file, function(tasks) {
    for(var i in tasks) {
      console.log(tasks[i]);
    }
  });
}

It includes three callback functions divided in two functions. 它包括分为三个函数的三个回调函数。 listTasks(..) is called first and it calls loadorInitializeTaskArray(.. ) later. 首先调用listTasks(..),然后再调用loadorInitializeTaskArray(..)。 My problem starts here, how is this call handle by node? 我的问题从这里开始,这个调用如何按节点处理? loadOrInitializeTaskArray takes two arguments and the second one is the callback function which shouldn't accept any parameters according to is signature but it does!! loadOrInitializeTaskArray有两个参数,第二个是回调函数,根据签名,它不应该接受任何参数,但是可以!
when does cb(..) get called in loadorInitializeTaskArray and what is that (the same function that calls helper function)? 什么时候在loadorInitializeTaskArray中调用cb(..),那是什么(调用辅助函数的同一函数)?

"tasks" is an array declared inside function loadOrInitializeTaskArray, how do we have access to it in listTasks(..) function? “任务”是在函数loadOrInitializeTaskArray中声明的数组,我们如何在listTasks(..)函数中访问它?

I know in Javascript, scope of a variable is inside the function it define and all nested functions. 我知道在Javascript中,变量的作用域位于它定义的函数和所有嵌套函数中。 But I have a hard time understanding it here. 但是我在这里很难理解。 So can someone explain what is going on here? 那么有人可以解释这里发生了什么吗? Thank you 谢谢

You really are having a hard time in understanding the scope of variable inside the nested functions. 您确实很难理解嵌套函数内部变量的范围。 So, lets start with it. 因此,让我们开始吧。
Let's consider this code 让我们考虑一下这段代码

function foo(){
  var a = 3;
  function bar(){
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz(); // the value of a i.e 3 will appear on console

If you know langauges like C, C++... Your brain would be interpreting this code like this. 如果您了解C,C ++等语言,您的大脑将像这样解释此代码。 If you don't know any of them just ignore thiese points. 如果您不知道它们中的任何一个,只需忽略这些想法。

  • When foo() is called, variable a will be declared and assigned value 3. 调用foo() ,将声明变量a并为其分配值3。
  • When foo is returned the stack containing a will be destroyed. foo返回时,包含a的堆栈将被销毁。 And hence a would be destroyed too. 因此a也将被销毁。
  • Then how in the world baz() is outputting 3 . 那么baz()到底如何输出3 ??? ???

Well, in javascript when a function is called the things that happen are different than in C. So, first let your all C things go out from your mind before reading further. 好吧,在javascript中调用函数时,发生的事情与C中发生的事情有所不同。因此,在进一步阅读之前,首先让您所有的C事情都从您的脑海中消失。

In javascript scope resolution is done by travelling down a chain of objects that defines variables that are "in scope" for that code. 在javascript中,范围解析是通过向下移动对象链来完成的,这些对象定义了该代码“范围内”的变量。 Let's see how? 让我们看看如何?

  • When you declare a global JavaScript variable, what you are actually doing is defining a property of the global object. 声明全局JavaScript变量时,实际上是在定义全局对象的属性。
  • In top-level JavaScript code (ie, code not contained within any function definitions), the scope chain consists of a single object, the global object. 在顶级JavaScript代码(即,任何函数定义中未包含的代码)中,作用域链由单个对象(全局对象)组成。
  • In a non-nested function, the scope chain consists of two objects. 在非嵌套函数中,作用域链由两个对象组成。 The first is the object that defines the function's parameters and local variables, and the second is the global object. 第一个是定义函数的参数和局部变量的对象,第二个是全局对象。
  • In a nested function, the scope chain has three or more objects. 在嵌套函数中,作用域链具有三个或更多对象。 When a function is defined, it stores the scope chain then in effect. 定义函数后,它将存储作用域链。 When that function is invoked, it creates a new object to store its local variables, and adds that new object to the stored scope chain to create a new, longer, chain that represents the scope for that function invocation. 调用该函数时,它将创建一个新对象以存储其局部变量,并将该新对象添加到存储的作用域链中,以创建一个新的,更长的链,该链表示该函数调用的作用域。

So, when foo() is executed. 因此,当执行foo()时。 It creates a new object to store it's local variables. 它创建一个新对象来存储其局部变量。 So, variable a will be stored in that object. 因此,变量a将存储在该对象中。 And also, bar is defined. 而且,bar被定义。 When bar is defined it stores the scope chain in effect. 定义bar时,它将存储有效的作用域链。 So, the scope chain of bar now contains the object that has variable a in it. 因此,bar的作用域链现在包含其中具有变量a的对象。 So, when bar is returned it has a reference to it's scope chain. 因此,当返回bar时,它具有对其范围链的引用。 And hence it knows a . 因此,它知道a

So, I guess it answers your question how node handles the code. 因此,我想它回答了您的问题,即节点如何处理代码。

You wrote: 你写了:

loadOrInitializeTaskArray takes two arguments and the second one is the callback function which shouldn't accept any parameters according to is signature but it does!! loadOrInitializeTaskArray有两个参数,第二个是回调函数,根据签名,它不应该接受任何参数,但是可以!

The callback function is 回调函数是

function(tasks) {
    for(var i in tasks) {
      console.log(tasks[i]);
    }
}

And it accepts an argument tasks . 并且它接受一个参数tasks So, you are wrong here. 所以,你错了。

And When loadOrIntializeTaskArray is called cb refers to this call back function. 并且在loadOrIntializeTaskArray ,cb引用此回调函数。 And cb(arg) basically does this tasks = arg where tasks is the argument in callback function. cb(arg)基本上执行此tasks = arg ,其中task是回调函数中的参数。

I guess you still will be having a lot of questions. 我想您仍然会有很多问题。 Let me know in comments. 在评论中让我知道。 And I highly recommend you to go through the Core Javascript before diving into node. 并且我强烈建议您在进入节点之前通读Core Javascript。

I'm not sure what you mean by "the second one is the callback function which shouldn't accept any parameters according to is signature". 我不确定您的意思是“第二个是回调函数,它不应该根据签名接受任何参数”。 The callback signature ( function(tasks) ) certainly does expect an argument ( tasks ). 回调签名( function(tasks) )当然确实需要参数( tasks )。

cb is the callback function that was passed in. In this case it is literally the anonymous function: cb是传入的回调函数。在这种情况下,它实际上是匿名函数:

function(tasks) {
  for(var i in tasks) {
    console.log(tasks[i]);
  }
}

tasks is the name of two different variables (but they point to the same object because arrays are passed by reference) in different scopes. tasks是在不同范围内的两个不同变量的名称(但它们指向同一对象,因为数组是通过引用传递的)。 One is defined in loadOrInitializeTaskArray() as you noted, the other is a parameter for the callback passed to loadOrInitializeTaskArray() . 如前所述,一个是在loadOrInitializeTaskArray()定义的,另一个是传递给loadOrInitializeTaskArray()的回调的参数。

Also, on an unrelated note, typically callbacks follow the "error-first" format. 同样,在不相关的注释上,回调通常遵循“错误优先”格式。 This allows for handling of errors upstream. 这允许上游处理错误。 Modifying the code to follow this convention would result in: 修改代码以遵循此约定将导致:

function loadOrInitializeTaskArray(file, cb) {
  fs.exists(file, function(exists) {
    var tasks = [];
    if (exists) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err)
          return cb(err);

        var data = data.toString();

        // JSON.parse() throws errors/exceptions on parse errors
        try {
          var tasks = JSON.parse(data || '[]');
        } catch (ex) {
          return cb(ex);
        }

        cb(null, tasks);
      });
    } else {
      cb(null, []); 
    }
  });
}

function listTasks(file) {
  loadOrInitializeTaskArray(file, function(err, tasks) {
    if (err) throw err;
    for (var i in tasks) {
      console.log(tasks[i]);
    }
  });
}

First, there is not really any such thing as a function signature in javascript. 首先,在javascript中并没有真正的函数签名之类的东西。 You can pass as many or as few arguments to a function as you like. 您可以根据需要向函数传递尽可能多的参数。 The second argument to loadOrInitialiseTaskArray is simply assigned to the local variable cb when the function is called. 调用该函数时,仅将loadOrInitialiseTaskArray的第二个参数分配给局部变量cb。 The line cb(tasks) then invokes this value, so the second argument had better have been a function. 然后, cb(tasks)行调用此值,因此第二个参数最好是一个函数。

When you call loadOrInitializeTaskArray from listTasks , the second argument is indeed a function, and the first argument to this function is named tasks in its own scope. 当您从listTasks调用loadOrInitializeTaskArray时,第二个参数确实是一个函数,而该函数的第一个参数在其自己的作用域中被命名为tasks It isn't reaching into loadOrInitializeTaskArray and using the variable declared in that function's scope. 它没有达到loadOrInitializeTaskArray并使用该函数范围内声明的变量。 You explicitly passed in that value when you invoked cb . 您在调用cb时显式传递了该值。

The code would work the same and perhaps be easier to understand if we renamed the variables: 如果我们重命名变量,代码将相同,并且可能更易于理解:

function loadOrInitializeTaskArray(file, cb) {
  fs.exists(file, function(exists) {
    var taskArray = [];
    if (exists) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err) throw err;
        var data = data.toString();
        var taskArray = JSON.parse(data || '[]');
        cb(taskArray);
      });
    } else {
      cb([]); 
    }
  });
}

function listTasks(file) {
  loadOrInitializeTaskArray(file, function(listOfTasks) {
    for(var i in listOfTasks) {
      console.log(listOfTasks[i]);
    }
  });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM