How to read file with async/await properly? - node.js

I cannot figure out how async/await works. I slightly understand it but I can't make it work.
function loadMonoCounter() {
fs.readFileSync("monolitic.txt", "binary", async function(err, data) {
return await new Buffer( data);
});
}
module.exports.read = function() {
console.log(loadMonoCounter());
};
I know, I could use readFileSync, but if I do, I know I'll never understand async/await and I'll just bury the issue.
Goal: Call loadMonoCounter() and return the content of a file.
That file is incremented every time incrementMonoCounter() is called (every page load). The file contains the dump of a buffer in binary and is stored on a SSD.
No matter what I do, I get an error or undefined in the console.

Since Node v11.0.0 fs promises are available natively without promisify:
const fs = require('fs').promises;
async function loadMonoCounter() {
const data = await fs.readFile("monolitic.txt", "binary");
return Buffer.from(data);
}

To use await/async you need methods that return promises. The core API functions don't do that without wrappers like promisify:
const fs = require('fs');
const util = require('util');
// Convert fs.readFile into Promise version of same
const readFile = util.promisify(fs.readFile);
function getStuff() {
return readFile('test');
}
// Can't use `await` outside of an async function so you need to chain
// with then()
getStuff().then(data => {
console.log(data);
})
As a note, readFileSync does not take a callback, it returns the data or throws an exception. You're not getting the value you want because that function you supply is ignored and you're not capturing the actual return value.

This is TypeScript version of #Joel's answer. It is usable after Node 11.0:
import { promises as fs } from 'fs';
async function loadMonoCounter() {
const data = await fs.readFile('monolitic.txt', 'binary');
return Buffer.from(data);
}

You can use fs.promises available natively since Node v11.0.0
import fs from 'fs';
const readFile = async filePath => {
try {
const data = await fs.promises.readFile(filePath, 'utf8')
return data
}
catch(err) {
console.log(err)
}
}

You can easily wrap the readFile command with a promise like so:
async function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
reject(err);
}
resolve(data);
});
});
}
then use:
await readFile("path/to/file");

From node v14.0.0
const {readFile} = require('fs/promises');
const myFunction = async()=>{
const result = await readFile('monolitic.txt','binary')
console.log(result)
}
myFunction()

To keep it succint and retain all functionality of fs:
const fs = require('fs');
const fsPromises = fs.promises;
async function loadMonoCounter() {
const data = await fsPromises.readFile('monolitic.txt', 'binary');
return new Buffer(data);
}
Importing fs and fs.promises separately will give access to the entire fs API while also keeping it more readable... So that something like the next example is easily accomplished.
// the 'next example'
fsPromises.access('monolitic.txt', fs.constants.R_OK | fs.constants.W_OK)
.then(() => console.log('can access'))
.catch(() => console.error('cannot access'));

There is a fs.readFileSync( path, options ) method, which is synchronous.

const fs = require("fs");
const util = require("util");
const readFile = util.promisify(fs.readFile);
const getContent = async () => {
let my_content;
try {
const { toJSON } = await readFile("credentials.json");
my_content = toJSON();
console.log(my_content);
} catch (e) {
console.log("Error loading client secret file:", e);
}
};

I read file by using the Promise. For me its properly:
const fs = require('fs')
//function which return Promise
const read = (path, type) => new Promise((resolve, reject) => {
fs.readFile(path, type, (err, file) => {
if (err) reject(err)
resolve(file)
})
})
//example how call this function
read('file.txt', 'utf8')
.then((file) => console.log('your file is '+file))
.catch((err) => console.log('error reading file '+err))
//another example how call function inside async
async function func() {
let file = await read('file.txt', 'utf8')
console.log('your file is '+file)
}

You can find my approach below:
First, I required fs as fsBase, then I put the "promises" inside fs variable.
const fsBase = require('fs');
const fs = fsBase.promises
const fn = async () => {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
};
fn();

This produces a String from the contents of your file you dont need to use promises for this to work
const fs = require('fs');
const data = fs.readFileSync("./path/to/file.json", "binary");

see this example
https://www.geeksforgeeks.org/node-js-fs-readfile-method/
// Include fs module
var fs = require('fs');
// Use fs.readFile() method to read the file
fs.readFile('demo.txt', (err, data) => {
console.log(data);
})

Related

How to Mock `fs.promises.writeFile` with Jest

I am trying to mock the promise version of fs.writeFile using Jest, and the mocked function is not being called.
Function to be tested (createFile.js):
const { writeFile } = require("fs").promises;
const createNewFile = async () => {
await writeFile(`${__dirname}/newFile.txt`, "Test content");
};
module.exports = {
createNewFile,
};
Jest Test (createFile.test.js):
const fs = require("fs").promises;
const { createNewFile } = require("./createFile.js");
it("Calls writeFile", async () => {
const writeFileSpy = jest.spyOn(fs, "writeFile");
await createNewFile();
expect(writeFileSpy).toHaveBeenCalledTimes(1);
writeFileSpy.mockClear();
});
I know that writeFile is actually being called because I ran node -e "require(\"./createFile.js\").createNewFile()" and the file was created.
Dependency Versions
Node.js: 14.1.0
Jest: 26.6.3
-- Here is another attempt at the createFile.test.js file --
const fs = require("fs");
const { createNewFile } = require("./createFile.js");
it("Calls writeFile", async () => {
const writeFileMock = jest.fn();
jest.mock("fs", () => ({
promises: {
writeFile: writeFileMock,
},
}));
await createNewFile();
expect(writeFileMock).toHaveBeenCalledTimes(1);
});
This throws the following error:
ReferenceError: /Users/danlevy/Desktop/test/src/createFile.test.js: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: writeFileMock
Since writeFile is destructured at import time instead of being consistently referred as fs.promises.writeFile method, it cannot be affected with spyOn.
It should be mocked as any other module:
jest.mock("fs", () => ({
promises: {
writeFile: jest.fn().mockResolvedValue(),
readFile: jest.fn().mockResolvedValue(),
},
}));
const fs = require("fs");
...
await createNewFile();
expect(fs.promises.writeFile).toHaveBeenCalledTimes(1);
It make sense to mock fs scarcely because unmocked functions provide side effects and potentially have negative impact on test environment.
Mock "fs/promises" async functions in jest
Here is a simple example using fs.readdir(), but it would also apply to any of the other async fs/promises functions.
files.service.test.js
import fs from "fs/promises";
import FileService from "./files.service";
jest.mock("fs/promises");
describe("FileService", () => {
var fileService: FileService;
beforeEach(() => {
// Create a brand new FileService before running each test
fileService = new FileService();
// Reset mocks
jest.resetAllMocks();
});
describe("getJsonFiles", () => {
it("throws an error if reading the directory fails", async () => {
// Mock the rejection error
fs.readdir = jest.fn().mockRejectedValueOnce(new Error("mock error"));
// Call the function to get the promise
const promise = fileService.getJsonFiles({ folderPath: "mockPath", logActions: false });
expect(fs.readdir).toHaveBeenCalled();
await expect(promise).rejects.toEqual(new Error("mock error"));
});
it("returns an array of the .json file name strings in the test directory (and not any other files)", async () => {
const allPotentialFiles = ["non-json.txt", "test-json-1.json", "test-json-2.json"];
const onlyJsonFiles = ["test-json-1.json", "test-json-2.json"];
// Mock readdir to return all potential files from the dir
fs.readdir = jest.fn().mockResolvedValueOnce(allPotentialFiles);
// Get the promise
const promise = fileService.getJsonFiles({ folderPath: "mockPath", logActions: false });
expect(fs.readdir).toBeCalled();
await expect(promise).resolves.toEqual(onlyJsonFiles); // function should only return the json files
});
});
});
files.service.ts
import fs from "fs/promises";
export default class FileService {
constructor() {}
async getJsonFiles(args: FilesListArgs): Promise<string[]> {
const { folderPath, logActions } = args;
try {
// Get list of all files
const files = await fs.readdir(folderPath);
// Filter to only include JSON files
const jsonFiles = files.filter((file) => {
return file.includes(".json");
});
return jsonFiles;
} catch (e) {
throw e;
}
}
}
I know this is an old thread, but in my case, I wanted to handle different results from readFile (or writeFile in your case). So I used the solution Estus Flask suggested with the difference that I handle each implementation of readFile in each test, instead of using mockResolvedValue.
I'm also using typescript.
import { getFile } from './configFiles';
import fs from 'fs';
jest.mock('fs', () => {
return {
promises: {
readFile: jest.fn()
}
};
});
describe('getFile', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should return results from file', async () => {
const mockReadFile = (fs.promises.readFile as jest.Mock).mockImplementation(async () =>
Promise.resolve(JSON.stringify('some-json-value'))
);
const res = await getFile('some-path');
expect(mockReadFile).toHaveBeenCalledWith('some-path', { encoding: 'utf-8' });
expect(res).toMatchObject('some-json-value');
});
it('should gracefully handle error', async () => {
const mockReadFile = (fs.promises.readFile as jest.Mock).mockImplementation(async () =>
Promise.reject(new Error('not found'))
);
const res = await getFile('some-path');
expect(mockReadFile).toHaveBeenCalledWith('some-path', { encoding: 'utf-8' });
expect(res).toMatchObject('whatever-your-fallback-is');
});
});
Note that I had to cast fs.promises.readFile as jest.Mock in order to make it work for TS.
Also, my configFiles.ts looks like this:
import { promises as fsPromises } from 'fs';
const readConfigFile = async (filePath: string) => {
const res = await fsPromises.readFile(filePath, { encoding: 'utf-8' });
return JSON.parse(res);
};
export const getFile = async (path: string): Promise<MyType[]> => {
try {
const fileName = 'some_config.json';
return readConfigFile(`${path}/${fileName}`);
} catch (e) {
// some fallback value
return [{}];
}
};

How to return back a data from a function and use in nodejs

I was trying to read and file by using a function and need to print that data from main code. Below shown is my code.
getJsonData().then(function (jsonData) {
console.log(jsonData)
})
function getJsonData(){
var fs = require('fs');
var XLSX = require('xlsx');
let contents = fs.readFileSync("test.json");
let jsonData = JSON.parse(contents);
return jsonData ;
}
Ok first of All, that function isn't a promise, so you can't use .then. Here is how you would turn this code into a promise:
var fs = require('fs');
var XLSX = require('xlsx');
function getJsonData(){
return new Promise((resolve, reject) => {
let contents = fs.readFileSync("test.json");
if(contents == "undefined") {
reject("File contains no contents");
} else {
let jsonData = JSON.parse(contents);
resolve(jsonData);
}
})
}
You would then use the function like you did in the question:
getJsonData().then(function (jsonData) {
console.log(jsonData)
})
readFileSync doesn't return a promise, so you can't use .then() after getJsonData(), as getJsonData() doesn't return a promise.
You can simply use:
const fs = require('fs');
const results = getJsonData();
console.log(results);
function getJsonData() {
const contents = fs.readFileSync("test.json");
return JSON.parse(contents);
}

Error: await is only valid in async function when function is already within an async function

Goal: Get a list of files from my directory; get the SHA256 for each of those files
Error: await is only valid in async function
I'm not sure why that is the case since my function is already wrapped inside an async function.. any help is appreciated!
const hasha = require('hasha');
const getFiles = () => {
fs.readdir('PATH_TO_FILE', (err, files) => {
files.forEach(i => {
return i;
});
});
}
(async () => {
const getAllFiles = getFiles()
getAllFiles.forEach( i => {
const hash = await hasha.fromFile(i, {algorithm: 'sha256'});
return console.log(hash);
})
});
Your await isn't inside an async function because it's inside the .forEach() callback which is not declared async.
You really need to rethink how you approach this because getFiles() isn't even returning anything. Keep in mind that returning from a callback just returns from that callback, not from the parent function.
Here's what I would suggest:
const fsp = require('fs').promises;
const hasha = require('hasha');
async function getAllFiles() {
let files = await fsp.readdir('PATH_TO_FILE');
for (let file of files) {
const hash = await hasha.fromFile(i, {algorithm: 'sha256'});
console.log(hash);
}
}
getAllFiles().then(() => {
console.log("all done");
}).catch(err => {
console.log(err);
});
In this new implementation:
Use const fsp = require('fs').promises to get the promises interface for the fs module.
Use await fsp.readdir() to read the files using promises
Use a for/of loop so we can properly sequence our asynchronous operations with await.
Call the function and monitor both completion and error.

jest test in node.js is failing showing Matcher error

Jest test is failing. Matcher error: received value must have a length property whose value must be a number
Received has type: object
Received has value: {}
but object has a value and it is not {}
index.js file
const fs = require('fs');
select('company')
async function select(selector) {
await fs.readFile('./content.html', 'utf8', function (err, data) {
if (err) throw err;
regexForIds = new RegExp(/<([^\s]+).*?id="company".*?>(.+?)<\/\1>/gi);
matches = data.match(regexForIds);
const obj = {
length: matches.length
};
return obj
});
}
module.exports = select;
index.js file
const select = require('./');
test('select supports ids', () => {
expect(select('#company')).toHaveLength(1);
});
fs.readFile doesn't return a promise, so await fs.readFile won't work.
select is an async function and it doesn't return an obj, so expect(select('#company')).toHaveLength(1) won't work either.
You can fix the first point by wrapping fs.readFile with a Promise (note that there are other ways of fixing this, like using promisify):
const fs = require("fs");
async function select(selector) {
const obj = await new Promise((res, rej) => {
fs.readFile("./content.html", "utf8", function(err, data) {
if (err) rej(err);
regexForIds = new RegExp(/<([^\s]+).*?id="company".*?>(.+?)<\/\1>/gi);
matches = data.match(regexForIds);
const obj = {
length: matches.length,
};
res(obj);
});
});
return obj;
}
module.exports = select;
To fix the second point, you need to change the test slightly, by adding an await before calling select:
const select = require("./");
test("select supports ids", async () => {
expect(await select("#company")).toHaveLength(1);
});
You will probably have to change the location of ./content.html depending on how you run your tests.

How to make node fs async/await with classes?

I'm having a tricky time making a parser that can be asynchronous. I keep either getting a promise returned or gettings undefined returned. After reading some docs online, I'm pretty sure I need to institute a try/catch block, but I don't know where with the fs.readFile method. Any help would be greatly appreciated
const fs = require('fs')
class CSV {
constructor (fileName, buffer) {
this.fileName = fileName;
this.buffer = buffer;
}
async parseCSV () {
let csv = this.fileName
let buff = this.buffer
let stream = await fs.readFile(csv, buff, (err, data) => {
if (err) console.log(err)
data = data.split('\r').toString().split('\n')
this.returnData(data)
})
return stream
}
returnData(data) {
return data
}
}
let test = new CSV('./topsongs.csv', 'utf8')
let data = await test.parseCSV()
console.log(data)
module.exports = CSV
The await operator is used to wait for a Promise. It can only be used inside an async function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
fs.readFile doesn't return Promise, you need a Promise wrapper. You can either:
Promisify it manually with built-in util.promisify.
const fs = require('fs');
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
let stream;
try {
stream = await fs.readFile(csv, buff); // callback omitted, `data` param returned instead
} catch (err) {
console.log(err);
}
Drop-in a 3rd party native fs replacement library like fs-extra.
const fs = require('fs-extra');
let stream;
try {
stream = await fs.readFile(csv, buff);
} catch (err) {
console.log(err);
}

Resources