revy
revy

Reputation: 4727

Node.js find a file with the same content async

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

Answers (2)

revy
revy

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

Devaraj C
Devaraj C

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

Related Questions