How to make node fs async/await with classes? - node.js

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);
}

Related

Return a promise in a node.js module

I want to return the data once it has been processed by convertToHtml, but it just return the data before the converting process is over. Any suggestions on how to implement it?
exports.extractData = async function(path) {
var mammoth = require("mammoth");
return await mammoth.convertToHtml({path: path}, options)
.then(function(result) {
var html = result.value;
var messages = result.messages;
return html;
}
}
Based on the suggestion I changed to:
exports.extractData = async function(path) {
var mammoth = require("mammoth");
const aux = await mammoth.convertToHtml({path: path}, options);
return aux.value;
}
}
Yet, I am getting:
Promise { <pending> }
I call the module like this:
var x = require("./index");
console.log(x.extractWord('docx'));
How can I obtain the result?
Thanks
Any async function returns a promise: you need to await your async function, something like this:
Give your module:
const mammoth = require("mammoth");
exports.extractData = async function(path) {
const aux = await mammoth.convertToHtml({path: path}, options);
return aux.value;
}
You can then say something like this:
const {extractData} = require('whatever-my-module-name-is');
async function main() {
const extractedData = await extractData();
process( extractedData ) ;
}
main()
.then( () => process.exit(0) )
.catch( err => {
console.error(err);
process.exit(1);
});
You don't need .then when you are using async/await. async/await is just a neat replacement for promises, you shouldn't use them together
exports.extractData = async function(path) {
var mammoth = require("mammoth");
return await mammoth.convertToHtml({path: path}, options);
}

How do I store axios response so that I can use it in different functions

I have a nodejs project and I need to store the response data of this axios request
let px2resp;
async function sendPx2() {
try {
let data = await axios.post('https://collector-pxajdckzhd.px-cloud.net/api/v2/collector', qs.stringify(PX2data), {
headers: PX2headers
});
px2resp = data;
return px2resp;
} catch (e) {
return e;
}
}
the way that I'm doing it right now is
let testing = async () => {
var a = await sendPx2();
console.log(a)
}
testing();
But the issue with this is that it makes the request everytime I want to use the data which is no ideal. Is there anyway for me to store this response data and use it without having to make the request multiple times?
(Assuming you don't want to store data in a file)
This may be the approach you are looking for:
let px2resp;
let alreadyFetched = false;
let cachedData = null;
async function sendPx2() {
// check if data was fetch already (to avoid same request multiple times)
// if so, return previous fetched data
if (alreadyFetched) return cachedData;
// else fetch data
let data = await axios.post(
'https://collector-pxajdckzhd.px-cloud.net/api/v2/collector',
qs.stringify(PX2data),
{
headers: PX2headers,
}
).catch((e) => console.log(e)); // This is optional (use try/catch if you still want)
// set variables
alreadyFetched = true;
cachedData = data;
return data;
}
And you can still use your existing code as normal but this time it will not fetch the data everytime if it was already fetched before.
let testing = async () => {
var a = await sendPx2();
console.log(a)
}
testing();
You can store the data inside a JSON file... Using the writeFile function in Node JS :)
fs = require('fs');
fs.writeFile('helloworld.json', px2resp, function (err) {
if (err) return console.log(err);
console.log('Data Written');
});
Then read it from the file to use in the Functions.
fs = require('fs')
fs.readFile('helloworld.json', 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
console.log(data);
});
Make sure to add utf-8, so it will not return Buffer Data.
You can store the data on a file
var fs = require("fs");
const util = require('util');
const readFile = util.promisify(fs.readFile);
const expiry = 10*60*1000 // 10 minutes
function cachedFunction(fn){
return async (...params) => {
let path = `${fn.name}:${params.join(":")}.json`
if (fs.existsSync(path)) {
let rawdata = await readFile(path, 'utf-8');
let response = JSON.parse(rawdata.toString());
console.log(response)
if(response.created_at + expiry > Date.now()){
console.log("getting from cache")
return rawdata
}
}
const data = await fn(...params)
if(data && typeof data === 'object'){
const stringifiedData = JSON.stringify({...data,created_at: Date.now()})
fs.writeFileSync(path, stringifiedData);
}
return data
}
}
async function hello(){
return {a:1,b:2}
}
async function test() {
console.log(await cachedFunction(hello)("x"))
}
test()

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);
}

Calling asynchronous recursive functions in Node.js with Promise

at first i need to say im pretty new to electron and node.js.
What i am doing is im trying to send an array with some directory data to my browser and this asynchronous (Error: array is empty).
The Problem should be my function 'scanDirectories(path)'.
If i make it non recursive (scanDirectories(res) -> only return res) it works pretty fine for 1 level directories, but recursive it doesnt.
So i think i need to do it like my function above with returning a new promise?
I tried it but can´t figure out how it works or my syntax should be.
Hope you can help me.
Simon
main.js:
calling function from ipcmain
ipcMain.on('fileTree', (event, arg) => {
let fileDirectory = helperFile.getDirectories(arg);
fileDirectory.then(function(result) {
event.reply('fileTree', result);
}, function(err) {
console.log(err);
})
})
files.js
const { readdir } = require('fs').promises;
const resolvePath = require('path').resolve;
module.exports = {
getDirectories: async function(path) {
return new Promise(function (resolve, reject) {
try {
resolve(scanDirectories(path));
} catch {
reject('Error');
}
});
}
};
async function scanDirectories(path) {
const dirents = await readdir(path, {withFileTypes: true});
const files = dirents.map((dirent) => {
const res = resolvePath(path, dirent.name);
return dirent.isDirectory() ? scanDirectories(res) : res;
});
return Array.prototype.concat(...files);
}
You can try something like this where you generate an array of promises:
files.js
const { readdir } = require('fs').promises;
const resolvePath = require('path').resolve;
module.exports = {
// This is an async function so don’t need internal promise
getDirectories: async function(path) {
try {
const dirs = await scanDirectories(path);
return dirs;
}
catch {
throw new Error('Error');
}
}
};
async function scanDirectories(path) {
const dirents = await readdir(path, {withFileTypes: true});
// Generate array of promises
const promises = dirents.map(dirent => {
const res = resolvePath(path, dirent.name);
return dirent.isDirectory()
? scanDirectories(res)
: Promise.resolve(res);
});
// Await all promises
const files = await Promise.all(promises);
return Array.prototype.concat(...files);
}
If you call an async function without await, you receive a promise.
Your event handler handles this sort-of-OK with then (it has trouble with error handling), but your recursive call to scanDirectories does not.
The simplest way to wait for an async function to resolve is to use await.
So this change makes the recursive call properly:
return dirent.isDirectory() ? (await scanDirectories(res)) : res;
Note the addition of await.
However "Array.map" is not designed for use in async functions. It will call them synchronously and create an array of promises. Not what you want.
In addition, this code is unnecessarily complicated, wrapping a promise in a promise and using try/catch in a way that won't work:
getDirectories: async function(path) {
return new Promise(function (resolve, reject) {
try {
resolve(scanDirectories(path));
} catch {
reject('Error');
}
});
}
Just call scanDirectories directly from your original event handler, and make the event handler an async function, so a lot of code just goes away.
In general: if you have to deal with async stuff, write an async function, and always await it in the function that calls it, even if that function is itself. You may write async functions anywhere, even if they are event handlers or Express routes or other contexts where the promise they resolve to won't be consumed.
Here is your original code simplified and corrected but working basically the same way:
ipcMain.on('fileTree', async (event, arg) => {
try {
event.reply('fileTree', await helperFile.scanDirectories(arg);
} catch (e) {
console.log(e);
}
});
// files.js
const { readdir } = require('fs').promises;
const resolvePath = require('path').resolve;
module.exports = {
scanDirectories: async function(path) {
const dirents = await readdir(path, { withFileTypes: true });
const files = [];
for (const dirent of dirents) {
const res = resolvePath(path, dirent.name);
if (dirent.isDirectory()) {
files = files.concat(await scanDirectories(res));
} else {
files.push(res);
}
});
return files;
}
}

How to read file with async/await properly?

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);
})

Resources