File I/O Basics and Callbacks in Node

Using asynchronous callbacks to perform file operations in Node.js.

Scroll down...

Content

Resources

Comments

Now that you understand more about how Node.js implements it's event-driven I/O, it is time to see the code that executes some of these operations. This lesson covers the basics of how to work with files and asynchronous callbacks in Node.js.

Basic File Operations with fs

File operations are handled by the File System module or fs as you'll see it in code. As you can see in the Node.js fs documentation, there are many operations. However, we'll be dealing with a few very specific methods that perform the basics.

  1. Read a file - fs.readFile: get the contents of a file as data
  2. Write a file - fs.writeFile: write data to a file
  3. Append to a file - fs.appendFile: add data to an existing file

Async vs Sync in the fs Module

As explained in the documentation for the fs module, all of the operations have both synchronous and asynchronous methods. This means that if you wish, you can perform the operation synchronously and wait for the process to complete before allowing your program to move on to the next statement. However, this is not the Node.js way.

As we've already pointed out, what makes Node.js so powerful is that it uses a non-blocking event loop. So how do we take advantage of this in our code?

Let's consider an example:

// Sync
var data = fs.readFileSync(path, 'utf8');
console.log(data);

// Async
fs.readFile(path, 'utf8', function(err, data) {
  err ? console.error(err) : console.log(data);
});

In the first part of this example, we see an assignment to a variable called data to the return value of fs.readFileSync(...). This makes sense. Synchronous means that execution waits for the last statement to finish before moving on to the next statement. Thus, the data from the file can be returned from the method call and output on the next line. However, this pattern is not possible with asynchronous programming.

In the second example you see that because we're passing a callback we must output the result inside the callback. If we attempted to capture the value and output it outside the scope of the callback it would be undefined because execution continued and the value of data wouldn't be set until the callback fires. Here is an example:

var _data;

fs.readFile(path, 'utf8', function(err, data) {
  _data = data;
  err ? console.error(err) : console.log(data);
});

console.log(_data); //=> undefined

In order to take advantage of non-blocking I/O we must use callbacks to execute code only once events have completed successfully.

One last point worth noting is a convention in Node.js called "error first callbacks".

Error First Callbacks

Node.js callback dog

A reoccurring theme in Node.js is the first parameter of callback functions being an error object. This is a convention in place so that there is no mystery as to where to locate the error or identify if an error has occurred. Many times when performing asynchronous tasks you'll want to know if they completed successfully, not just whether they completed at all. You will find the same convention used throughout Node.js and its native/third-party libraries.

// Error first callback
// The err object is always the first
// parameter of the callback function
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
  if (err) throw err;
  console.log('It\'s saved!');
});

Code Review

Here are the important snippets of code from this lesson.

// Sync
var data = fs.readFileSync(path, 'utf8');
console.log(data);

// Async
fs.readFile(path, 'utf8', function(err, data) {
  err ? console.error(err) : console.log(data);
});
// Common error of attempting
// to assign a variable
// to the value in a scope
// of a callback
var _data;

fs.readFile(path, 'utf8', function(err, data) {
  _data = data;
  err ? console.error(err) : console.log(data);
});

console.log(_data); //=> undefined
// Error first callback
// The err object is always the first
// parameter of the callback function
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
  if (err) throw err;
  console.log('It\'s saved!');
});

Wrapping Up

Understanding the basics of how Node.js uses callbacks to perform asynchronous operations is key to leveraging Node's non-blocking event loop. Further, knowledge of the "error first callbacks" convention will help you write and comprehend Node libraries and ultimately be a better Node.js developer.



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

Sorry, comments aren't active just yet!