workshopper / learnyounode

Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.
Other
7.25k stars 1.84k forks source link

[learnyounode - Exercise 4] Question about scopes and async #470

Closed jamiewilson closed 8 years ago

jamiewilson commented 8 years ago

Hey there.

Here's my question, with more context below

Why is that the logMyNumber function in the first example is able to access the myNumber variable as is, while my printResults function needs numberOfLines passed as an argument?


I read the Art of Node section about callbacks as suggested in this exercise and it helped me come to my solution here. But, it did bring up something that confused me. Here is the Art of Node example of passing in the logMyNumber function as the callback for addOne:

// via https://github.com/maxogden/art-of-node#callbacks

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)

And, similarly I created a function printResults to use as my callback, but only returned undefined unless I passed in the numberOfLines variable, as shown below:

// learnyounode Exercise 4 of 13

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

function getNumberOfLines(callback) {
  fs.readFile(process.argv[2], function(error, contents) {
    if (error)
      console.log('Nope. `fs.readFile` function has an error')
    var numberOfLines = contents.toString().split('\n').length - 1
    callback(numberOfLines)
  })
}

function printResults(numberOfLines) {
  console.log(numberOfLines);
}

getNumberOfLines(printResults)

Thanks for the help! P.S. Thanks to everyone who's contributed to this project. It's really cool.

kid-icarus commented 8 years ago

Hello @jamiewilson!

Your issue stems from:

var numberOfLines = contents.toString().split('\n').length - 1

When you use the var keyword, it creates a new binding available in the immediate execution context.

When you do something like, console.log(foo) it will look for a foo variable in its immediate execution context, and then work up each parent execution context in the the scope chain, until at last it reaches the global exectuion context.

This might be of help https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

If you remove that the var from var numberOfLines = contents.toString().split('\n').length - 1 and also remove the numberOfLines from the line function printResults(numberOfLines) {, you should observe the desired behavior.

Hope that helps!

jamiewilson commented 8 years ago

:facepalm: Of course! This makes perfect sense now. thanks for that explanation @kid-icarus.

kid-icarus commented 8 years ago

You're welcome @jamiewilson, if you haven't read JavaScript Allongé, it's to read free online and I recommend it.

Here's a little bit on scope/closures:

https://leanpub.com/javascriptallongesix/read#closures