Reputation: 4727
The goal: given a file path and an array of file paths in input, I need to check if there is a file in the array (even with a different name) that is equal to the input file. I need to read the files asynchronously and stop if an equal file is found (in that case I don't want to read all the files in the array).
I would like to use only ecma6 features without additional libraries. I am using node.js, thus to compare the files is sufficient to use the compare buffers function:
const fs = require("fs");
let buffer1 = fs.readFileSync(filePath1);
let buffer2 = fs.readFileSync(filePath2);
if (buffer1.compare(buffer2) === 0) {
console.log("Files are equal");
} else {
console.log("Files are different");
}
Basically for each of the file path in the array I want to check equality sequentially (but with asynchronous read) with a function like this:
function isFileEqual (fileInputBuffer, path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) {
resolve(false);
}
if (data.compare(fileInputBuffer) === 0) {
resolve(true);
} else {
resolve(false);
}
});
});
}
I came up with a solution taking advantage of the "reject fast" property of Promises.all, which cause to reject immediately in case any of the input promises reject without wait for the other promises to complete:
const fs = require("fs");
let paths = ["file2", "file3", "file1_copy", "file4", "file5"];
checkIfFileAlreadyExistsAsync("file1", paths).then(path => {
console.log("\n", "File equal found at: ", path, "\n");
}).catch(err => {
console.log(err);
});
function checkIfFileAlreadyExistsAsync(filePath, paths) {
return new Promise( (rootResolve, rootReject) => {
fs.readFile(filePath, (err, inputBuffer) => {
if (err) {
rootReject(err);
return;
}
function isFileEqual(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
console.log("[isFileEqual]", path);
if (err) {
resolve();
}
else if (data.compare(inputBuffer) === 0) {
reject(path); // file equal found, reject fast!
} else {
resolve();
}
});
});
}
let promises = [];
// fill promises array
paths.forEach(path => {
promises.push(isFileEqual(path));
})
Promise.all(promises).then(values => {
rootReject(false);
})
.catch((path) => {
// use reject fast to resolve without wait the other promises to complete
rootResolve(path);
});
});
});
}
Output of the above script:
[isFileEqual] file2
[isFileEqual] file1_copy
File equal found at: file1_copy
[isFileEqual] file4
[isFileEqual] file3
[isFileEqual] file5
The above solution works but as you can see there is a problem: all the files are always read regardless if an equal file has already been found.
I didn't know before, but a Promise is executed as soon as it is created (I though the opposite, why was it implemented in this way?), thus I was thinking to use a promise factory like the following:
function promiseFactory(path) {
return function () {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
console.log("[isFileEqual]", path);
if (err) {
reject();
}
else if (data.compare(inputBuffer) === 0) {
resolve(true);
} else {
resolve(false);
}
});
});
};
}
and try to run the promises in sequence. But how can I do that? Or are there alternative ways?
Upvotes: 0
Views: 331
Reputation: 4727
I found a solution using recursion. Basically I create an array of factories (each factory returns a function which returns a Promise) and then use that array as input for a recursive function. If an equal file is found, the recursive function resolve the main Promise (the one returned by the main function), otherwise it call itself recursively with in input the array of factories and the index of the next promise to create.
const fs = require("fs");
let paths = ["file2", "file3", "file1_copy", "file4", "file5"];
checkIfFileAlreadyExistsAsync("file1", paths).then(result => {
console.log("SUCCESS", result);
}).catch(err => {
console.log("FAIL", err);
});
function checkIfFileAlreadyExistsAsync(filePath, paths) {
return new Promise((rootResolve, rootReject) => {
fs.readFile(filePath, (err, inputBuffer) => {
if (err) {
rootReject(err);
}
function compareFilePromiseFactory(path) {
return function() {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
console.log("Compare file: ", path, "\n");
if (err) {
resolve(false);
}
else if(data.compare(inputBuffer) === 0) {
resolve(true);
}
else {
resolve(false);
}
});
});
}
}
let factories = [];
paths.forEach(path => {
factories.push(compareFilePromiseFactory(path));
});
function findFile(factories, index) {
if (index == factories.length) {
rootReject(false);
return;
}
factories[index]().then(result => {
if (result) {
rootResolve(true);
}
else {
findFile(factories, index + 1);
}
});
}
findFile(factories, 0);
});
});
}
The above script gives the following output:
Compare file: file2
Compare file: file3
Compare file: file1_copy
SUCCESS true
Each promise is created in sequence but it is all async. It stops to generate promises as soon as an equal file is found. I don't know if it is also possible an iterative solution, what do you think?
Upvotes: 1
Reputation: 337
Promise.race
Promise.race is an interesting function -- instead of waiting for all promises to be resolved or rejected, Promise.race triggers as soon as any promise in the array is resolved or rejected:
var req1 = new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() { resolve('First!'); }, 8000);
});
var req2 = new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() { resolve('Second!'); }, 3000);
});
Promise.race([req1, req2]).then(function(one) {
console.log('Then: ', one);
}).catch(function(one, two) {
console.log('Catch: ', one);
});
// From the console: // Then: Second!
Check this one, I believe it will help you
Upvotes: 0