0

I have a test folder with files

  • file
  • file (1)
  • file (2)

If the file exists I add a suffix to a new filename, to prevent overwriting the file. For example

  • if file exists new name should be file (1)
  • if file (1) exists new name should be file (2)
  • if file (2) exists new name should be file (3)

and so on.

The following function works fine, except the value is not returned so I can assign it later.

async function dest_exists_new_name(file) {

    const access = fs.promises.access

    try {

        await access(file, fs.F_OK)

        // file exists - generate new name
        const info = path.parse(file)
        const dir = info.dir
        let name = info.name
        const ext = info.ext 

        // generate suffix
        let suffix = ' (1)'
        const suffix_regex = / \([0-9]+\)$/

        if (suffix_regex.test(name)) {              // if suffix exists -> increment it

            const num = name.split(' ').reverse()[0].replace(/[()]/g,'')
            const next_num = parseInt(num) + 1
            suffix = ' (' + next_num + ')'
            name = name.replace(suffix_regex, '')   // remove old suffix
        } 
        // generate suffix end

        const new_name = path.join(dir, name + suffix + ext) 

        // recurse until dest not exists
        await dest_exists_new_name(new_name)

      } catch {

        // file not exist - return its name

        // console.log works OK
        console.log('new name ->', file) 

        // return doesn't work - returns undefined if the previous name exists, but works ok if the name doesn't exists
        return file 
      }
}
await dest_exists_new_name('/path/file')

new name -> /path/file (3) // console.log - works OK
undefined                  // returns undefined, if file previously exists

The question is how can I correctly return the new file name value?

If there are any culprits in such a solution like

  • accidental file rewriting
  • infinite recursion
  • other issues

I will be grateful for the hints on how to improve the function.

3 Answers 3

1

Your function will return file, but being an async function, you need to await its return and you cannot do so outside of an async scope. Thus, if you just console.log() its "istantaneous" value, it will indeed return a pending promise, as the return value has not been resolved yet. You may retrieve the correct return value by including your function in an async scope, like this:

let a = async () => {
    console.log(await dest_exists_new_name('/path/file'))
  }

a();

This will output:

new name -> /path/file
/path/file //here's your file

Now, by adding return await dest_exists_new_name(new_name) you should be able to achive what you want and both console.log() and return the new, non-existent, file name. Here's a complete, reproducible example:

const fs = require('fs');
const path = require('path');

async function dest_exists_new_name(file) {

  const access = fs.promises.access

  try {

    await access(file, fs.F_OK)

    const info = path.parse(file)
    const dir = info.dir
    let name = info.name
    const ext = info.ext

    let suffix = ' (1)'
    const suffix_regex = / \([0-9]+\)$/

    if (suffix_regex.test(name)) {
      const num = name.split(' ').reverse()[0].replace(/[()]/g, '')
      const next_num = parseInt(num) + 1
      suffix = ' (' + next_num + ')'
      name = name.replace(suffix_regex, '')
    }

    const new_name = path.join(dir, name + suffix + ext)

    return await dest_exists_new_name(new_name)

  } catch {

    console.log('new name ->', file)

    return file
  }
}

//Here, make sure that the path to "file" is correct 

let a = async() => console.log(await dest_exists_new_name(path.join(__dirname, './file')));

a();

Output, having up to file (2) in the same folder:

new name -> /path/to/file (3)
/path/to/file (3)
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the great explanation! It makes sense. I've added await while calling the function, but the function returns undefined if the filename previously exists. console.log works fine. Could you have a look?
Thanks for the hint. But we do get console.log output in the catch block when the file doesn't exist. It happens on the last recursive iteration. If we put return file in the try block it will return the first existing file. But I need to return a non-existent file with the appropriate suffix. Just like in the console.log in catch close. It outputs file (3) which doesn't exist. Check out the function output in my question.
Looks like it still returns 1st file when testing with file name. But if the folder contains files like file, file (1) file (2) it should return file (3) which doesn't exist but not file. Try to create 3 files on the folder like above and run the function on them.
Interestingly, that is what I had done, and I get file (3) in return. Do you not? Just to be sure, I edited my answer with the complete code I used for the test
Thanks for the full code. I just give it a try. It still returns file for me instead of file (3) What node version do you use? I have v12.8.1 fs.promises.access was added in v10.0.0
|
1

Check you try catch and how you are receiving your variable.

async function dest_exists_new_name(file) {
    try {
      const res = await dest_exists_new_name(file1); // recursion result
    } catch (err) {
       return Promise.resolve("file not found");
    }
}

// usage
let res = await dest_exists_new_name(fileArg);

Comments

1

First of all, you should use await, since it's async function:

        // recurse until dest not exists
        await dest_exists_new_name(new_name)

About recursion - IMHO, it's always better to use cycle (if it doesn't make code too complicated).

Mixing async-await & promises is not very good. Ideally you should use one style.

I prefer to use destructuring, lambda functions, and other modern features.

So, my variant for async-await, without recursion:

const fs = require('fs');
const path = require('path');

// better to create outside of func, since it doesn't depend on context
const suffix_regex = / \([0-9]+\)$/
const defaultSuffix = ' (1)'

const access = async (...params) => new Promise((resolve) => fs.access(...params, (err) => (err) ? resolve(false) : resolve(true)))

const generate_new_suffix = ({ dir, name, ext }) => {
  if (suffix_regex.test(name)) { // if suffix exists -> increment it
    const num = name.split(' ').reverse()[0].replace(/[()]/g, '')
    const suffix = `(${+num + 1})`;
    const cleanName = name.replace(suffix_regex, '')   // remove old suffix
    return path.join(dir, cleanName + suffix + ext)
  }
  return path.join(dir, name + defaultSuffix + ext)
}

const dest_exists_new_name = async (file) => {
  let newNameGenerated = false
  let newFileName = file
  while (await access(newFileName, fs.F_OK)) {
    console.log(newFileName);
    const info = path.parse(newFileName)
    newFileName = generate_new_suffix(info)
  };

  console.log('new name ->', newFileName)
  return newFileName
}

(async () => {
  console.log(await dest_exists_new_name(path.join(__dirname, 'file')))
})();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.