- The Old Way of Doing Things
- The Node.js Way of Doing Things
- Error Handling and Asynchronous Functions
- Who Am I? Maintaining a Sense of Identity
- Being Polite-Learning to Give Up Control
- Synchronous Function Calls
- Summary
Who Am I? Maintaining a Sense of Identity
Now you’re ready to write a little class to help you with some common file operations:
var fs = require('fs'); function FileObject () { this.filename = ''; this.file_exists = function (callback) { console.log("About to open: " + this.filename); fs.open(this.filename, 'r', function (err, handle) { if (err) { console.log("Can't open: " + this.filename); callback(err); return; } fs.close(handle, function () { }); callback(null, true); }); }; }
You have currently added one property, filename, and a single method, file_exists. This method does the following:
- It tries to open this file specified in the filename property read-only.
- If the file doesn’t exist, it prints a message and calls the callback function with the error info.
- If the file does exist, it calls the callback function indicating success.
Now, run this class with the following code:
var fo = new FileObject(); fo.filename = "file_that_does_not_exist"; fo.file_exists(function (err, results) { if (err) { console.log("Aw, bummer: " + JSON.stringify(err)); return; } console.log("file exists!!!"); });
You might expect the following output:
About to open: file_that_does_not_exist Can't open: file_that_does_not_exist
But, in fact, you see this:
About to open: file_that_does_not_exist Can't open: undefined
What happened? Most of the time, when you have a function nested within another, it inherits the scope of its parent/host function and should have access to all the same variables. So why does the nested callback function not get the correct value for the filename property?
The problem lies with the this keyword and asynchronous callback functions. Don’t forget that when you call a function like fs.open, it initializes itself, calls the underlying operating system function (in this case to open a file), and places the provided callback function on the event queue. Execution immediately returns to the FileObject#file_exists function, and then you exit. When the fs.open function completes its work and Node runs the callback, you no longer have the context of the FileObject class anymore, and the callback function is given a new this pointer!
The bad news is that you have, indeed, lost your this pointer referring to the FileObject class. The good news is that the callback function for fs.open does still have its function scope. A common solution to this problem is to “save” the disappearing this pointer in a variable called self or me or something similar. Now rewrite the file_exists function to take advantage of this:
this.file_exists = function (callback) { var self = this; console.log("About to open: " + self.filename); fs.open(this.filename, 'r', function (err, handle) { if (err) { console.log("Can't open: " + self.filename); callback(err); return; } fs.close(handle, function () { }); callback(null, true); }); };
Because function scope is preserved via closures, the new self variable is maintained for you even when your callback is executed asynchronously later by Node.js. You will make extensive use of this in all your applications. Some people like to use me instead of self because it is shorter; others still use completely different words. Pick whatever kind you like and stick with it for consistency.