2

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.

ponury-kostek
  • 7,824
  • 4
  • 23
  • 31
Systems Rebooter
  • 1,281
  • 2
  • 14
  • 30
  • Have you considered using the async-await for this? – cross19xx Aug 23 '19 at 10:57
  • Use async module in nodejs and use `async.waterfall` .Check this link to see various options in async module. https://medium.com/velotio-perspectives/understanding-node-js-async-flows-parallel-serial-waterfall-and-queues-6f9c4badbc17 – VinuBibin Aug 23 '19 at 10:57
  • @cr05s19xx nope. To my regret I am lack of knowledge how to do this correctly. – Systems Rebooter Aug 23 '19 at 10:58

3 Answers3

5

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
  • 7,824
  • 4
  • 23
  • 31
2

@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()  
Systems Rebooter
  • 1,281
  • 2
  • 14
  • 30
0
  • 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.