22

I am experimenting with async/await code to read file. Here's my code:

var fs = require('fs');

function readFile(fileName) {
  return new Promise(resolve => {
    //console.log(test);
    fs.readFile(fileName, 'utf8', function (err, data) {
      if (err) throw err;

      console.log(fileName)
      console.log(data)
    })
    resolve();
  });
}

async function run() {
  await readFile('file1.txt');
  await readFile('file2.txt');
  readFile('file3.txt');
}

run();

But the result is still random. It means file3 sometime read before file2. Where am I doing wrong?

roscoe_x
  • 609
  • 1
  • 9
  • 16
  • 2
    You put `resolve` *outside* of the asynchronous call, so it gets resolved immediately. – CertainPerformance Jan 04 '19 at 07:25
  • 1
    Possible duplicate of [How do I convert an existing callback API to promises?](https://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises) – CertainPerformance Jan 04 '19 at 07:25
  • Does this answer your question? [Using filesystem in node.js with async / await](https://stackoverflow.com/questions/40593875/using-filesystem-in-node-js-with-async-await) – KyleMit Nov 18 '19 at 22:19

2 Answers2

19

There are many ways to achieve that.

Most of them is explained in this link


I'll write simple one:

1) using util.promisify to convert callback method to promise:

const fs = require('fs');
const util = require('util');
const readFile = (fileName) => util.promisify(fs.readFile)(fileName, 'utf8');

(async () => {
  try {
    const files = ['file1.txt', 'file2.txt', 'file3.txt'];
    for (const file of files) {
      console.log(
        await readFile(file)
      );
    }
  }
  catch (error) {
    console.error(error);
  }
})();

2) *Sync methods. Since Your code is not dealing with concurrency You can use *Sync methods:

const fs = require('fs');

try {
  const files = ['file1.txt', 'file2.txt', 'file3.txt'];
  for (const file of files) {
    console.log(
      fs.readFileSync(file, 'utf8')
    );
  }
}
catch (error) {
  console.error(error);
}


BTW. Here is Your fixed code:

var fs = require('fs');

function readFile(fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, 'utf8', function (error, data) {
      if (error) return reject(error);

      console.log(fileName)
      console.log(data)

      resolve();
    })
  });
}

async function run() {
  await readFile('file1.txt');
  await readFile('file2.txt');
  await readFile('file3.txt');
}

run();

since You're calling readFile and resolve at same async sequence it's being called at same time which is reason of race condition.

You've to wait for callback handling and then resolve it (inside callback scope).

num8er
  • 18,604
  • 3
  • 43
  • 57
  • 1
    Thank you for showing other methods to achieve my goal... Actually there are some additional code to process the data. Yes, the code works by moving resolve() to inside fs.readFile(). Alternative 2 is easier to understand. – roscoe_x Jan 04 '19 at 07:48
19

There are a couple options with native node functionality

A) With the fs.promises API

You can use destructuring assignment on import to alias fs.promises as just fs

const { promises: fs } = require("fs");

(async () => {
    try {
        let file1 = await fs.readFile("file1.txt", "utf-8");
        let file2 = await fs.readFile("file2.txt", "utf-8");

    } catch (e) {
        console.log("e", e);
    }
})()

B) With util.promisify API

const fsSync = require("fs");
const {promisify} = require("util")

const fs = {
  readdir: promisify(fsSync.readdir),
  readFile: promisify(fsSync.readFile),
  // etc
};

(async () => {
    try {
        let file1 = await fs.readFile("file1.txt", "utf-8");
        let file2 = await fs.readFile("file2.txt", "utf-8");

    } catch (e) {
        console.log("e", e);
    }
})()

Further Reading

KyleMit
  • 30,350
  • 66
  • 462
  • 664