Rename files asynchronously in Node.js if destination files don't exist - javascript

I am trying to rename files asynchronously in Node.js only if destination files don't exist.
I made a quick test like follows:
const fs = require('fs')
const files = [ 'file1', 'file2', 'file3' ]
const new_name = 'new-name' // same destination name for all
fs.exists() - DEPRECATED
for (let file of files)
fs.exists(new_name, (exists) => {
if (!exists) fs.rename(file, new_name, (err) => {})
})
fs.access() - RECOMMENDED
for (let file of files)
fs.access(new_name, fs.constants.F_OK, (err) => {
if (err) fs.rename(file, new_name, (err) => {})
})
fs.move() - from fs-extra
const fs_extra = require('fs-extra')
for (let file of files)
fs_extra.move(file, new_name, { overwrite: false }, (err) => {})
Each time all 3 files were overwriten and renamed to one file.
I believe this is happens because all exists checks fire sooner than any rename happens.
I know how to accomplish this task synchronously, but want to be sure that there is no proper async way to do so.

You can create Promise which resolve's when file is renamed
fs.rename(file, new_name, (err) => {
resolve(); <------
});
or when renaming is skipped
fs.access(new_name, fs.constants.F_OK, (err) => {
if (err) {
return fs.rename(file, new_name, (err) => {
resolve();
});
}
resolve(); <------
});
Full code
(async () => {
for (let file of files) {
await new Promise((resolve) => {
fs.access(new_name, fs.constants.F_OK, (err) => {
if (err) {
return fs.rename(file, new_name, (err) => {
resolve();
});
}
resolve();
});
});
}
})();
and if you don't want to mix async/await with Promise
(async () => {
function rename(file, new_name) {
return new Promise((resolve) => {
fs.access(new_name, fs.constants.F_OK, (err) => {
if (err) {
return fs.rename(file, new_name, (err) => {
resolve();
});
}
resolve();
});
});
}
for (let file of files) {
await rename(file, new_name);
}
})();

#ponury-kostek solution works brilliantly and marked as accepted answer.
I ended up with the following code since it's a bit shorter:
async function rename_files() {
for (let file of files)
await fs.move(file, new_name)
}
rename_files()

Instead of wrapping fs library in promises.
I like to import the promise implementation of the fs library.
Then call the fs methods with await.
.
import {promises as fs_promises} from 'fs'; // The promise implmentation of fs library.
async function renameFile() {
const fileFullPath = '1234.txt';
const newFileFullPath = '5678.txt';
await fs_promises.rename(fileFullPath, newFileFullPath, (error) => {
if (error) {
console.log(error);
} else {
console.log("\nFile Renamed\n");
}
});
}
await renameFile(); // Call the async method.

Related

How to read files present in array nodejs

I would like to know to read the files and search for keyword sample in nodejs.
If keyword found, display the path
const allfiles = [
'C:\\Users\\public',
'C:\\Users\\public\\images',
'C:\\Users\\public\\javascripts\\index1.js',
'C:\\Users\\public\\javascripts\\index2.js'
]
const readFile = (path, opts = 'utf8') =>
new Promise((resolve, reject) => {
try{
let result=[];
fs.readFile(path, opts, (err, data) => {
if (err) reject(err)
else {
if(data.indexOf("sample")>=0){
result.push(data);
resolve(result);
}
}
})
}
catch (e) {
console.log("e", e);
}
})
const run = async () => {
allfiles.forEach(e=>{
const s = await readFile(e);
console.log(s);
})
}
run();
Expected Output
[
'C:\\Users\\public\\javascripts\\index1.js',
'C:\\Users\\public\\javascripts\\index2.js'
]
Some tips:
What happens when "sample" isn't found in readFile?
You're currently pushing the data into result instead of the path.
Think about what you're trying to accomplish with readFile. To me, what you want to do is see if that file has the word "sample", and return true if so and if not return false. So I'd name the function checkIfFileHasSample and have it return a boolean. Then in your run function, in the forEach you have the path, so that is where I'd add the path to a list of results.
Maybe you already realized this, but run is never actually called in your code sample. Ie. run() doesn't happen.
Solution:
You had some syntax errors and a tricky gotcha with async-await with run. For the syntax errors, it'll come with experience, but I'd also recommend using ESLint to help you catch them, as well as making sure your code is always properly indented.
const fs = require("fs");
const allfiles = [
"C:\\Users\\public",
"C:\\Users\\public\\images",
"C:\\Users\\public\\javascripts\\index1.js",
"C:\\Users\\public\\javascripts\\index2.js",
];
const checkIfFileHasSample = (path, opts = "utf8") =>
new Promise((resolve, reject) => {
fs.readFile(path, opts, (err, data) => {
if (err) {
reject(err);
} else {
if (data.includes("sample")) {
resolve(true);
} else {
resolve(false);
}
}
});
});
const run = async () => {
const results = [];
for (let i = 0; i < allFiles.length; i++) {
try {
const file = allFiles[i];
const hasSample = await checkIfFileHasSample(file);
if (hasSample) {
results.push(file);
}
} catch (e) {
console.log(e);
}
}
console.log(results);
};
run();

Wrap a FTP request in Async/Await

I am trying to perform a FTP request, wait till the file has downloaded and then close the FTP module. When both these actions have finished, then list the contents of a directory. At the moment, it is doing it in the opposite direction.
I have wrapped them in async and prepended the FTP with await. But the directory list is being logged first. Can spot the error in the async function?
(async function () {
await Ftp.get("document.txt", "document.txt", err => {
if (err) {
return console.error("There was an error retrieving the file.");
}
console.log("File copied successfully!");
Ftp.raw("quit", (err, data) => {
if (err) {
return console.error(err);
}
console.log("Bye!");
});
});
})()
// Read the content from the /tmp directory to check it's empty
fs.readdir("/", function (err, data) {
if (err) {
return console.error("There was an error listing the /tmp/ contents.");
}
console.log('Contents of tmp file above, after unlinking: ', data);
});
First, await only works with promises, and ftp.get apparently uses a callback instead of a promise. So you'll have to wrap ftp.get in a promise.
Second, your fs.readdir is outside of the async function, so it is not going to be affected by the await. If you need it to be delayed, then you need it to be inside the async function, after the await statement.
So put together that looks something like this:
(async function () {
await new Promise((resolve, reject) => {
Ftp.get("document.txt", "document.txt", err => {
if (err) {
reject("There was an error retrieving the file.")
return;
}
console.log("File copied successfully!");
Ftp.raw("quit", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
})
});
fs.readdir("/", function (err, data) {
if (err) {
return console.error("There was an error listing the /tmp/ contents.");
}
console.log('Contents of tmp file above, after unlinking: ', data);
});
})()
I usually try to separate things out. It looks like you wanted to save a file so I built this out with that in mind. I put each request into it's own promise. I don't think you need Ftp.raw. I am not sure if Ftp is the node library or simply the var name of another library.
const util = require("util");
const fs = require("fs");
const fsOpen = util.promisify(fs.open);
const fsWriteFile = util.promisify(fs.writeFile);
const fsClose = util.promisify(fs.close);
async function saveDocumentAndListDirectoryFiles() {
let documentData;
let fileToCreate;
let listDirectoryFiles
//We get the document
try {
documentData = await getDocument();
} catch (error) {
console.log(error);
return;
}
// Open the file for writing
try {
fileToCreate = await fsOpen("./document.txt", "wx");
} catch (err) {
reject("Could not create new file, it may already exist");
return;
}
// Write the new data to the file
try {
await fsWriteFile(fileToCreate, documentData);
} catch (err) {
reject("Error writing to new file");
return;
}
// Close the file
try {
await fsClose(fileToCreate);
} catch (err) {
reject("Error closing new file");
return;
}
// List all files in a given directory
try {
listDirectoryFiles = await listFiles("/");
} catch (error) {
console.log("Error: No files could be found");
return;
}
console.log(
"Contents of tmp file above, after unlinking: ",
listDirectoryFiles
);
};
// Get a document
function getDocument() {
return new Promise(async function(resolve, reject) {
try {
await Ftp.get("document.txt", "document.txt");
resolve();
} catch (err) {
reject("There was an error retrieving the file.");
return;
}
});
};
// List all the items in a directory
function listFiles(dir) {
return new Promise(async function(resolve, reject) {
try {
await fs.readdir(dir, function(err, data) {
resolve(data);
});
} catch (err) {
reject("Unable to locate any files");
return;
}
});
};
saveDocumentAndListDirectoryFiles();

Node JS async/await with multiple fs.writeFile using through2 (Gulp/Vinyl)

I'm using through2 to generate multiple files from a Gulp stream. I'm using NodeJS 10.6.0 so thought I'd make full use of async/await, but am not fully understanding the mechanics yet. Currently the through2 done() callback is being fired before all files have been written.
Here's what I have (simplified) - note that I'm not returning the stream at the end as there is no need to.
async function createDirectory(pathDir) {
return new Promise((resolve, reject) => {
mkdirp(pathDir, (err) => {
if (err) reject(err);
else resolve();
});
});
}
async function writeFile(outputFilePath, outputFileContent) {
return new Promise((resolve, reject) => {
fs.writeFile(outputFilePath, outputFileContent, (err) => {
if (err) reject(err);
else resolve();
});
});
}
async function doWriteFile(outputFolderPath, outputFilePath, outputContent) {
await createDirectory(outputFolderPath);
await writeFile(outputFilePath, outputContent, outputContent);
}
async function doGenerateVariant(data, variantArr) {
for (const variant of variantArr) {
/* Do a load of stuff */
const variantOutputFolderPath = blah;
const variantOutputFilePath = blah;
const variantOutputContent = blah;
await doWriteFile(variantOutputFolderPath, variantOutputFilePath, variantOutputContent);
}
}
const generateVariant = () => {
return through.obj((file, enc, done) => {
const data = JSON.parse(file.contents.toString());
*/ Do a load of stuff */
const { variant } = data;
const variantArr = Object.values(variant);
doGenerateVariant(data, variantArr);
return done();
});
};
This doesn't work as done() gets returned before all files have been written. I'm guessing I'm missing a return or two but nothing I do seems to be working.
If I pass done() into doGenerateVariant and call it after doWriteFile everything works as expected but I know this isn't correct.
You need to wait for doGenerateVariant to do its job before calling done. Remember async function always returns a Promise. So you could do it this way
const generateVariant = () => {
return through.obj((file, enc, done) => {
const data = JSON.parse(file.contents.toString());
*/ Do a load of stuff */
const { variant } = data;
const variantArr = Object.values(variant);
doGenerateVariant(data, variantArr).then(() => done());
});
};
or using async/await
const generateVariant = () => {
return through.obj(async (file, enc, done) => {
const data = JSON.parse(file.contents.toString());
*/ Do a load of stuff */
const { variant } = data;
const variantArr = Object.values(variant);
await doGenerateVariant(data, variantArr);
done();
});
};

migrating from data.task to folktale on handling rejection

In data.task package, I could resolve or reject a api call as following:
import Task from 'data.task';
import fs from 'fs';
const readFile = (filename, enc) => {
return new Task((rej, res) =>
fs.readFile(filename, enc, (err, contents) => {
err ? rej(err) : res(contents);
})
);
};
How would I accomplish that in the new folktale version of Task? I can resolve requests, but how do I reject? I have tried the following:
const {task, rejected} = require('folktale/concurrency/task');
import fs from 'fs';
const readFile = (filename, enc) => {
return task(res => {
fs.readFile(filename, enc, (err, contents) => {
err ? rejected(err) : res.resolve(contents);
});
});
};
const writeFile = (filename, contents) => {
return task(res => {
fs.writeFile(filename, contents, (err, success) => {
err ? rejected(err) : res.resolve(success);
});
});
};
const app = readFile('FILE_DOESNOT_EXIST.json', 'utf-8')
.map(contents => contents.replace(/8/g, '6'))
.chain(contents => writeFile('config1.json', contents));
app.run().listen({
onCancelled: () => {
console.log('the task was cancelled');
},
onRejected: () => {
console.log('something went wrong');
},
onResolved: value => {
console.log(`The value is Good`);
},
});
When I gave a file that doesn't exist, the onRejected handler does not get called.
What do I expect to see:
Since I have the program read a file that does not exist, it should run onRejected which should log something went wrong.
What do I see now:
Nothing. The program does not bug out, but it also does not produce anything, it simply runs as normal.
When using data.task(the older version of Task), I can use reject which is why it stills works there. How do I do it now with the new version of Task?
Ok this is really silly! For some reason I could not find this solution right away on the doc.That's why I imported the rejected from task...
Basically resolver function coming from task has not only resolve, but also reject, which should have been obvious, but it was not on the doc.
So here is working code:
import {task} from 'folktale/concurrency/task';
import fs from 'fs';
const readFile = (filename, enc) => {
return task(res => {
fs.readFile(filename, enc, (err, contents) => {
err ? res.reject() : res.resolve(contents);
});
});
};
const writeFile = (filename, contents) => {
return task(res => {
fs.writeFile(filename, contents, (err, success) => {
err ? res.reject() : res.resolve(success);
});
});
};
const app = readFile('confg.json', 'utf-8')
.map(contents => contents.replace(/8/g, '6'))
.chain(contents => writeFile('config1.json', contents));
app.run().listen({
onCancelled: () => {
console.log('the task was cancelled');
},
onRejected: () => {
console.log('something went wrong');
},
onResolved: value => {
console.log(`The value is Good`);
},
});

NodeJS concatenate all files in a directory

Is there a faster or more succinct way to concatenate all of the files located in a directory using NodeJS?
In bash I could do something like this:
for file in $1
do
cat "$file"
echo
done > $2;
Here is what I'm doing now:
var fs = require('fs');
var Promise = require('bluebird');
module.exports = function(directory, destination) {
return new Promise((resolve, reject) => {
fs.readdir(directory, (err, files) => {
if (err) {
return reject(err);
}
(function next() {
var file = files.shift();
if (!file) {
return resolve();
}
fs.readFile(directory + '/' + file, (err, content) => {
if (err) {
return reject(err);
}
fs.appendFile(destination, '\n' + content, (err) => {
if (err) {
return reject(err);
}
return next();
});
});
})();
});
});
};
That ?
require('child_process').execSync('cat *').toString('UTF-8')
:D
Using the async library you can easily read files in parallel and then join the results.
const fs = require("fs");
const async = require("async");
const path = require("path");
module.exports = function(directory, destination) {
return new Promise((resolve, reject) => {
fs.readdir(directory, (err, files) => {
if (err)
return reject(err);
files = files.map(file => path.join(directory,file));
//Read all files in parallel
async.map(files, fs.readFile, (err, results) => {
if (err)
return reject(err);
//results[0] contents of file #1
//results[1] contents of file #2
//results[n] ...
//Write the joined results to destination
fs.writeFile(destination, results.join("\n"), (err) => {
if (err)
return reject(err);
resolve();
});
});
});
});
}
If you're going to use bluebird than you get the benefit of promisification. You can use promisifyAll() to convert all error first callback accepting async functions in the fs module to return a promise. You can read more about in the above promisification link.
The below code reads in all of the files as strings and then reduces all of their contents into a single string and writes that string to the destination.
Its probably best to not catch() any returned errors here. Rather, the caller should attach a catch() to handle any returned errors as they need.
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs'))
const path = require('path')
module.exports = (directory, destination) => {
return fs.readdirAsync(directory)
.map(file => fs.readFileAsync(path.join(directory, file), 'utf8'))
.then(contents => fs.writeFileAsync(destination, contents.join('\n')))
}
All in one line:
fs.readdirSync('./').forEach((file) => { if(fs.lstatSync(file).isFile()) fs.appendFileSync('./output.file', fs.readFileSync(file).toString()) })
Replace './' with target directory and './output.file' with target destination.
You can do the same thing without bluebird , since node 8.X includes the util package to promisify fs API.
This exemple shows how i use it in my project to concatenate minified files (so remove the filter if necessary).
You can do the same thing without bluebird , since node 8.X includes the util package to promisify fs API.
This exemple shows how i use it in my project to concatenate minified files (so remove the filter if necessary).
const {promisify} = require("util"); //requires node 8.X
const readdir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);
const appendFile = promisify(fs.appendFile);
// Append all minified and obsfucated files in source directory
// The resulting file is generated in destination
function appendFile(directory, destination) {
readdir(directory)
.then((files) => {
console.log('FILES CONTENT:', files);
files.filter(file => {
console.log('FILTER > ' + file);
return (file.indexOf('-min.js') != -1 && file.indexOf('-min.js.map') == -1)
})
.map(file => {
console.log('MAP ('+destination+') > ' + path.join(directory, file));
readFile(path.join(directory, file), 'utf8')
.then(data => {
//console.log('DATA:', data);
appendFile(destination, data+'\n')
.then(() => {
console.log('append done');
})
.catch((err) => {
displayError(err);
});
});
});
})
.catch((err) => {
console.log('ERROR:', err);
displayError(err);
});
}

Categories