Как ждут рекурсивное обещание в Javascript - PullRequest
2 голосов
/ 23 мая 2019

Я написал рекурсивное Обещание в javascript, которое, кажется, работает нормально, но я хотел протестировать его с помощью setTimeout (), чтобы убедиться, что я правильно жду, прежде чем продолжить выполнение. Вот суть моего кода:

try{
  await renameFiles(); // <-- await here
  console.log("do other stuff");
}
catch(){
}

const renameFiles = (path) => {
  return new Promise(resolve => {
    console.log("Renaming files...");

    fs.readdirSync(path).forEach(file) => {
      // if file is a directory ...
      let newPath = path.join(path, file);
      resolve( renameFiles(newPath) ); // <- recursion here!
      // else rename file ...
    }
    resolve();
  })

Я проверил это с помощью setTimeout () следующим образом:

const renameFiles = () => {
  return new Promise(resolve => {
    setTimeout(() => {
    // all previous code goes here
    },2000)
  }
}

и вывод:

"Renaming files..."
"Renaming files..."
// bunch of renaming files...
"do other stuff"
"Renaming files..."
"Renaming files..."

Похоже, что он немного ждет, но затем продолжает выполнение в какой-то момент.

Я тоже сомневаюсь, что проверяю это неправильно. Любая идея, где проблема может быть?

Ответы [ 6 ]

3 голосов
/ 23 мая 2019

Как уже упоминалось - многократные вызовы разрешения не имеют смысла. Однако это не единственная проблема в коде. Корневой вызов был разрешен, когда начался его рекурсивный вызов для первого подкаталога. Этот код будет обрабатывать каталоги в иерархическом порядке

rename.js

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

const inputPath = path.resolve(process.argv[2]);
const newName = 'bar.txt';

async function renameFiles(filePath) {
    for (const file of fs.readdirSync(filePath)) {
        const newPath = path.join(filePath, file);
        const descriptor = fs.lstatSync(newPath);
        if (descriptor.isDirectory()) {
            await renameFiles(newPath)
        } else if (descriptor.isFile()) {
            await renameFile(file);
        }
    }
}

async function renameFile(file) {
    console.log(`Renaming ${file} to ${newName}`)
    return new Promise(resolve => {
       setTimeout(() => {
           console.log(`Renamed ${file} to ${newName}`)
           resolve();
       }, 300)
    });
}

async function main() {
    console.log(`Renaming all files in ${inputPath} to ${newName}`);
    await renameFiles(inputPath);
    console.log('Finished');
}

main();

вы можете запустить его как

node rename.js relativeFolderName

или, если порядок не имеет значения, вы можете использовать map и Promise.all, как упомянуто @Tiago Coelho

const renameFiles = async path => {
    const renamePromises = fs.readdirSync(path).map(file => {
      if (isDirectory(file)) {
          const newPath = path.join(path, file);
          return renameFiles(newPath)
      } else {
          return renamefile(file);
      }  
    });
    await Promise.all(renamePromises);
}
0 голосов
/ 25 мая 2019

В моем первом ответе я показал вам, как решить вашу проблему, используя в основном функциональные методы. В этом ответе мы увидим, что современные функции JavaScript, такие как асинхронные итерации, делают такие вещи еще проще -

const files = async function* (path = ".")
{ if ((await stat (path)) .isDirectory ())
    for (const f of await readdir (path))
      yield* files (join (path, f))
  else
     yield path
}

const search = async function* (query, path = ".")
{ for await (const f of files (path))
    if (query === basename (f))
      yield f
}

const renameFiles = async (from = "", to = "", path = ".") =>
{ for await (const f of search (from, path))
    await rename
      ( f
      , join (dirname (f), to)
      )
}

renameFiles ("foo", "bar", "path/to/someFolder")
  .then (_ => console .log ("done"), console.error)
0 голосов
/ 25 мая 2019

Вместо того, чтобы писать одну большую сложную функцию, я предложу более разложенный подход.

Сначала мы начнем с files, который рекурсивно перечисляет все файлы с указанным path -

const { readdir, stat } =
  require ("fs") .promises

const { join } =
  require ("path")

const files = async (path = ".") =>
  (await stat (path)) .isDirectory ()
    ? Promise
        .all
          ( (await readdir (path))
              .map (f => files (join (path, f)))
          )
        .then
          ( results =>
             [] .concat (...results)
          )
    : [ path ]

У нас есть способ перечислить все файлы сейчас, но мы хотим переименовать только некоторые из них.Мы напишем обобщенную функцию search, чтобы найти все файлы, которые соответствуют запросу -

const { basename } =
  require ("path")

const search = async (query, path = ".") =>
  (await files (path))
    .filter (x => basename (x) === query)

Теперь мы можем написать вашу renameFiles функцию как специализацию search -

const { rename } =
  require ("fs") .promises

const { dirname } =
  require ("path")

const renameFiles = async (from = "", to = "", path = ".") =>
  Promise
    .all
      ( (await search (from, path))
          .map
            ( f =>
                rename
                  ( f
                  , join (dirname (f), to)
                  )
             )
       )

Чтобы использовать его, мы просто вызываем renameFiles с его ожидаемыми параметрами -

renameFiles ("foo", "bar", "path/to/someFolder")
  .then
    ( res => console .log ("%d files renamed", res.length)
    , console.error
    )

// 6 files renamed

Рассматривая наши программы выше, мы видим некоторые паттерны, возникающие при использовании Promise.all, await и map.Действительно, эти шаблоны могут быть извлечены, и наши программы могут быть еще более упрощены.Вот files и renameFiles, пересмотренные для использования универсального модуля Parallel -

const files = async (path = ".") =>
  (await stat (path)) .isDirectory ()
    ? Parallel (readdir (path))
        .flatMap (f => files (join (path, f)))
    : [ path ]

const renameFiles = (from = "", to = "", path = "") =>
  Parallel (search (from, path))
    .map
      ( f =>
          rename
            ( f
            , join (dirname (f), to)
            )
      )

Модуль Parallel был первоначально получен в в этой связанной Q & A .Для получения дополнительной информации и объяснений, пожалуйста, перейдите по ссылке.

0 голосов
/ 24 мая 2019

Для полноты картины я выложу все решение на основе предложения @ udalmik .Разница лишь в том, что я обертываю async function renameFile(file) внутри Promise .

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

const inputPath = path.resolve(process.argv[2]);
const newName = 'bar.txt';

async function renameFiles(filePath) {
    for (const file of fs.readdirSync(filePath)) {
        const newPath = path.join(filePath, file);
        const descriptor = fs.lstatSync(newPath);
        if (descriptor.isDirectory()) {
            await renameFiles(newPath)
        } else if (descriptor.isFile()) {
            await renameFile(file);
        }
    }
}

async function renameFile(file) {
  return new Promise(resolve => {
    console.log(`Renaming ${file} to ${newName}`);
    resolve();
  })
}

async function main() {
    console.log(`Renaming all files in ${inputPath} to ${newName}`);
    await renameFiles(inputPath);
    console.log('Finished');
}

main();

Причина использования Promise заключается в том, что я хочу дождаться переименования всех файлов перед продолжением выполнения (т. Е. console.log('Finished');).

I'm проверено это с использованием setTimeout

return new Promise(resolve => {
    setTimeout(()=>{
      console.log(`Renaming ${file} to ${newName}`);
    },1000)
    resolve(); // edited missing part
  })

Решение пошло не так, как мой первоначальный вопрос, но я думаю, что оно работает для меня.

0 голосов
/ 23 мая 2019

Чтобы это работало, вам нужно дождаться разрешения всех файлов в каталоге.Поэтому вам нужно будет сделать Promise.all и использовать map вместо forEach

примерно так:

try{
  await renameFiles(); // <-- await here
  console.log("do other stuff");
}
catch(){
}

const renameFiles = (path) => {
  return new Promise(resolve => {
    console.log("Renaming files...");

    const allFilesRenamePromises = fs.readdirSync(path).map(file => {
      if(file.isDirectory()) {
        let newPath = path.join(path, file);
        return renameFiles(newPath); // <- recursion here!
      } else {
        // rename file ...
      }
    }
    resolve(Promise.all(allFilesRenamePromises));
  })
0 голосов
/ 23 мая 2019

попробуйте изменить код ожидания следующим образом.это может вам помочь.

try{
  const renameFilesPromise = renameFiles();
  renameFilesPromise.then({      <-- then is a callback when promise is resolved
    console.log("do other stuff");
  })
}
catch(){
}

const renameFiles = (path) => {
  return new Promise(resolve => {
    console.log("Renaming files...");

    fs.readdirSync(path).forEach(file) => {
      // if file is a directory ...
      let newPath = path.join(path, file);
      resolve( renameFiles(newPath) ); // <- recursion here!
      // else rename file ...
    }
    resolve();
  })
...