2

I know this has been asked a million times, but I'm really trying to break down async Javascript functions and callbacks and its just not clicking. I'm looking at Max Ogden's Art of Node example which is this:

var fs = require('fs')
var myNumber = undefined

function addOne(callback) {
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    myNumber = parseInt(fileContents)
    myNumber++
    callback()
  })
}

function logMyNumber() {
  console.log(myNumber)
}

addOne(logMyNumber)

Breaking this down, I understand that when addOne is invoked, it first kicks off fs.ReadFile which may take some time to complete.

What I don't get is, won't the code continue to callback() and execute logMyNumber (before myNumber has been added to) anyhow? What's stopping callback() from running before it should, which is the whole point? Or does callback() not happen until doneReading has happened? Are we supposed to assume that doneReading will be invoked when fs.readFile is "done"?

Thank you all for your patience in helping me with this very common question:)

bdkauff
  • 225
  • 3
  • 13
  • did you read the API docs for those methods?, they explain the functions work. did you run it and see how it works? step debugger is your friend. there is no *timing* to callbacks, they are serialized. –  Nov 08 '13 at 20:05
  • 1
    `callback()` is called by `doneReading`, itself a callback. And boo-hiss for relying on semicolon insertion. – Dave Newton Nov 08 '13 at 20:06
  • 1
    You don't **assume** - it's there in the documentation http://nodejs.org/api/fs.html#fs_fs_readfile_filename_options_callback – Adam Jenkins Nov 08 '13 at 20:09
  • 1
    @Adam Although it doesn't actually *say* the function is called when the file has been read, no? – Dave Newton Nov 08 '13 at 20:10
  • @DaveNewton yes it does say when the function is called, and what it is called with `The callback is passed two arguments (err, data), where data is the contents of the file.` Can't pass the contents of the file until the contents are read completely! –  Nov 08 '13 at 20:12
  • 3
    @JarrodRoberson So it implies it, but personally, I wouldn't consider it particularly explicit. When someone is flailing, being explicit is actually helpful. – Dave Newton Nov 08 '13 at 20:14
  • Thanks for the help, everyone. It sounds silly, but the fact that doneReading was the callback for readFile seemingly needed to be bashed over my head:) Also, I realize this topic is a total duplicate, but when abstraction is what you're contending with, sometimes its hard to look at another's post and see the similarities with your question. – bdkauff Nov 08 '13 at 20:31
  • 1
    I think it was less a problem with the documentation that with the example. I happened to need an explicit _reminder_ that `doneReading` was the callback. I know empirically that it must be the callback. Make sense? Everyone learns differently I suppose. – bdkauff Nov 08 '13 at 20:44

5 Answers5

2

"Are we supposed to assume that doneReading will be invoked when fs.readFile is "done"?"

You dont have to assume it, you can be pretty sure of it. You can use logging to see how and in what order your code gets executed.

var fs = require('fs')

console.log("starting script");
console.log("message 1");
function addOne(callback) {
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    console.log("finished loading the file");
    console.log("message 2");
    callback()
  })
}
console.log("message 3");
//logMyNumber will be called after file has read
function logMyNumber() {
  console.log("message 4");
}
console.log("message 5");
addOne(logMyNumber)
console.log("message 6");

//______________ A simpler way to understand the asyncronous behavior is to use all familiar timer

console.log("message 1");
var num = 2;

function something() {
    console.log("message 2");
}
function somethingElse() {
    console.log("message 3");
}
console.log("message 4");

setTimeout(something, 1000);
console.log("message 5");
setTimeout(somethingElse, 500);

//code will run 1 - 4 - 5 - 3- 2 not from top to bottom, and this way its obvious why. //in file read its the same reason

Rainer Plumer
  • 3,693
  • 2
  • 24
  • 42
0

This is the way the code will flow:

  1. You call to addOne(logMyNumber) will be executed.
  2. addOne will read a file and once the file has been read it will then
  3. execute the code in the "doneReading" function which in turn will call your callback (which is logMyNumber)
Hector Correa
  • 26,290
  • 8
  • 57
  • 73
0

See the second argument for fs.readFile? It's a function called doneReading.

fs.readFile will only execute doneReading when it has finished reading the file. When doneReading gets executed, the last line is to call callback(), which is a reference to the logMyNumber function in this case.

Travis
  • 12,001
  • 8
  • 39
  • 52
  • So somewhere at the end of `fs.readFile()` is `doneReading()`, itself a callback, correct? – bdkauff Nov 08 '13 at 20:12
  • Yes, at the end of `fs.readFile()`, there's a line of code that says to execute whatever the 2nd argument is. In this case, the second argument is a function called `doneReading()`. That's all a callback is. – Travis Nov 08 '13 at 20:14
0

fs.readFile will call the given callback, ie. doneReading in your code after the reading has been finished. This is how Node.js generally works with the callbacks: you give the callback which is ran after finishing the asynchronous operation.

Outoluku
  • 40
  • 4
0
// callback is a parameter to addOne
// callback is a function, but functions are just objects in javascript
// so the addOne function just knows that it has one parameter, not that
// callback is a function
function addOne(callback) {

  // callback is now captured in the closure for the doneReading function
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    myNumber = parseInt(fileContents)
    myNumber++

    // callback is executed here
    // But we are inside the doneReading function
    // which is itself a callback to the fs.readFile function
    // therefore, it does not get executed until the file has finished reading
    callback()
  })
}

// similarly, logMyNumber has not been called, it has just been defined
// as a function (object)...
function logMyNumber() {
  console.log(myNumber)
}

// ...and passing logMyNumber to addOne here does not execute it
addOne(logMyNumber)

Does that clear it up?

Patrick M
  • 10,547
  • 9
  • 68
  • 101