Home » Nodejs » node.js readfile woes

node.js readfile woes

Posted by: admin December 21, 2017 Leave a comment

Questions:

The following piece of code creates a text file and then reads it, overwrites it, and reads it again. Except the creation of the file the three I/O operations are performed using Node.js async readFile and writeFile.

I don’t understand why the first read is returning no error but no data either. The output of this code is:

  • Starting…
  • Done.
  • first read returned EMPTY data!
  • write finished OK
  • second read returned data: updated text

Even if the operations were to happen in an arbitrary order (due to their async nature) I would have NOT expected to get an “empty data” object.

Any ideas why I am getting an empty data when reading the file (and no error) ?

Is there anything that I can do to make sure the file content is read?

var fs = require('fs');
var fileName = __dirname + '/test.txt';

// Create the test file (this is sync on purpose)
fs.writeFileSync(fileName, 'initial test text', 'utf8');


console.log("Starting...");

// Read async
fs.readFile(fileName, 'utf8', function(err, data) {
    var msg = "";
    if(err)
        console.log("first read returned error: ", err);
    else {
        if (data === null) 
            console.log("first read returned NULL data!");
        else if (data === "") 
            console.log("first read returned EMPTY data!");
        else
            console.log("first read returned data: ", data);
    }
});


// Write async
fs.writeFile(fileName, 'updated text', 'utf8', function(err) {
    var msg = "";
    if(err)
        console.log("write finished with error: ", err);
    else
        console.log("write finished OK");
});


// Read async
fs.readFile(fileName, 'utf8', function(err, data) {
    var msg = "";
    if(err)
        console.log("second read returned error: ", err);
    else
        if (data === null) 
            console.log("second read returned NULL data!");
        else if (data === "") 
            console.log("second read returned EMPTY data!");
        else
            console.log("second read returned data: ", data);
});


console.log("Done.");
Answers:

Your code is asking for race conditions. Your first sync write is probably writing the file, but then your first read, second write, and second read are put onto the event loop simultaneously.

What could have happened here? First read gets read permission from the filesystem, second write gets write permission from the filesystem and immediately zeroes the file for future updating, then the first read reads the now empty file. Then the second write starts writing data and the second read doesn’t get read permission until it’s done.

If you want to avoid this, you need to use the flow:

fs.writeFileSync(filename, 'initial', 'utf8');
fs.readFile(filename, 'utf8', function(err, data) {
    console.log(data);
    fs.writeFile(filename, 'text', 'utf8', function(err) {
        fs.readFile(filename, 'utf8', function(err, data) {
            console.log(data);
        });
    });
});

If that “pyramid” insults your programming sensibilities (why wouldn’t it?) use the async library‘s series function:

fs.writeFileSync(filename, 'initial', 'utf8');
async.series([
    function(callback) {
        fs.readFile(filename, 'utf8', callback);
    },
    function(callback) {
        fs.writeFile(filename, 'text', 'utf8', callback);
    },
    function(callback) {
        fs.readFile(filename, 'utf8', callback);
    }
], function(err, results) {
    if(err) console.log(err);
    console.log(results); // Should be: ['initial', null, 'text']
});

EDIT: More compact, but also more “magical” to people not familiar with the async library and modern Javascript features:

fs.writeFileSync(filename, 'initial', 'utf8');
async.series([
    fs.readFile.bind(this, filename, 'utf8'),
    fs.writeFile.bind(this, filename, 'text', 'utf8'),
    fs.readFile.bind(this, filename, 'utf8'),
], function(err, results) {
    if(err) console.log(err);
    console.log(results); // Should be: ['initial', null, 'text']
});

EDIT2: Serves me right for making that edit without looking up the definition of bind. The first parameter needs to be the this object (or whatever you want to use as this).