Node.js return 递归循环中的承诺

Node.js return a promise in recursive loop

我无法以正确的顺序循环 return 嵌套承诺。 我正在尝试递归循环遍历包含子目录的目录以获取文件并将它们复制到新目录。此功能发生在 Promise 链内,因为其他进程需要在复制文件后按顺序完成。

以下代码有效,但是当我 console.log 在 .then() 之后输出一个字符串时,它会在承诺解决之前进行记录。

密码是:

    let newDirectory = /// helper function that returns promise of mkdirSync(exampleDir)
    newDirectory.then(function(result){
        getAllFiles('/TestDir').then((result) => {
            console.log(result);
        });
        console.log('this should fire after the "result" from above');
        //// rest of promises in promise chain
    });
        

我调用“getAllFiles”的递归函数遍历“TestDir”及其子文件夹并将文件复制到“ExampleDir”

const getAllFiles = function(dirPath, arrayOfFiles) {
   return new Promise((resolve, reject) => {
   var promises = [];
   files = fs.readdirSync(dirPath)
   arrayOfFiles = arrayOfFiles || []
   files.forEach(function(file) {
     if (fs.statSync(dirPath + "/" + file).isDirectory()) {
        arrayOfFiles =  resolve(getAllFiles(dirPath + "/" + file, arrayOfFiles));
      } else {
      promises.push(helper.copyFile(path.join(dirPath, "/", file),`/TestDir/${file}`))
     }
   }
 })
  Promise.all(promises).then(function(result){
    resolve(result);
    console.log('done with Promises');
  }).catch((error) => {
    reject(error);
  });
}

复制文件的助手return在文件被复制后的承诺

exports.copyFile = function(file, destDir){
  return new Promise(function ( resolve, reject ){
        fs.copyFile(file, destDir, (err) => {
          if(err) reject( err );
          else resolve('Successfully copied');
        });
   });
}

这似乎确实可行,但我担心它无法处理大量数据,因为

console.log('this should fire after the "result" from above');

在其他日志之前触发。控制台看起来像:

this should fire after the "result" from above
done with Promises        /// how ever many files are copied
[ 'Successfully copied']   //// with a length of however many files are copied)
done with Promises        //// this is fired once

这是一个预期的日志,还是应该在记录“'this should fire after the "result" from above'”行之前解决并记录所有承诺?

如果你想在 Promise 之后控制日志,你必须在 .then() 中进行,比如

Promise.all(promises).then(function(result) {
  resolve(result);
  console.log('done with Promises');
})
  .then(() => console.log("this should fire after the "result" from above"))
  .catch((error) => reject(error));

这是因为 Promise 是 non-blocking 并且它之后的任何内容都不会等待它完成后再执行。

fs/promises 和 fs.Dirent

这是一个使用 Node 的快速 fs.Dirent objects and fs/promises 模块的高效 non-blocking ls 程序。这种方法允许您跳过每条路径上浪费的 fs.existfs.stat 调用。

使用 asyncawait,我们可以避免过多考虑如何具体连接 Promise -

// main.js
import { readdir } from "fs/promises"
import { join } from "path"

async function* ls (path = ".")
{ yield path
  for (const dirent of await readdir(path, { withFileTypes: true }))
    if (dirent.isDirectory())
      yield* ls(join(path, dirent.name))
    else
      yield join(path, dirent.name)
}

async function* empty () {}

async function toArray (iter = empty())
{ let r = []
  for await (const x of iter)
    r.push(x)
  return r
}

toArray(ls(".")).then(console.log, console.error)

让我们获取一些示例文件,以便我们可以看到 ls 工作 -

$ yarn add immutable     # (just some example package)
$ node main.js
[
  '.',
  'main.js',
  'node_modules',
  'node_modules/.yarn-integrity',
  'node_modules/immutable',
  'node_modules/immutable/LICENSE',
  'node_modules/immutable/README.md',
  'node_modules/immutable/contrib',
  'node_modules/immutable/contrib/cursor',
  'node_modules/immutable/contrib/cursor/README.md',
  'node_modules/immutable/contrib/cursor/__tests__',
  'node_modules/immutable/contrib/cursor/__tests__/Cursor.ts.skip',
  'node_modules/immutable/contrib/cursor/index.d.ts',
  'node_modules/immutable/contrib/cursor/index.js',
  'node_modules/immutable/dist',
  'node_modules/immutable/dist/immutable-nonambient.d.ts',
  'node_modules/immutable/dist/immutable.d.ts',
  'node_modules/immutable/dist/immutable.es.js',
  'node_modules/immutable/dist/immutable.js',
  'node_modules/immutable/dist/immutable.js.flow',
  'node_modules/immutable/dist/immutable.min.js',
  'node_modules/immutable/package.json',
  'package.json',
  'yarn.lock'
]

请参阅此 ,了解递归列出目录的 dir 程序、查找文件的 search 程序等。

Is this a log to be expected, or should all of the promises resolve and be logged before the "'this should fire after the "result" from above'" line is logged?

是的,这是意料之中的。 当 Promises 并不意味着同步时,您正在编写代码,就好像它是同步的。 下面的代码片段中实际发生的是在 newDirectory Promise 解析之后,getAllFilesconsole.log('this should fire after the "result" from above'); 函数都将立即执行。也就是说,console.log 不会等待 getAllFiles 解析后再执行。

let newDirectory = /// helper function that returns promise of mkdirSync(exampleDir)
newDirectory.then(function(result){
    getAllFiles('/TestDir').then((result) => {
        console.log(result);
    });
    console.log('this should fire after the "result" from above');
    //// rest of promises in promise chain
});

因此,如果您想更改 console.log 的顺序以确保它在 getAllFiles Promise 解析后执行,您可以重写如下。

newDirectory.then(function(result){
    getAllFiles('/TestDir').then((result) => {
        console.log(result);
        // or you could add it here
    }).then(() => { 
      console.log('this should fire after the "result" from above');
    });
});

您还应该注意到,我说的是 Promise 解析时,而不是函数完成执行时。有一个非常重要的区别。 如果我们再次以上面的例子为例,并说我们想在所有文件都被复制后执行一些其他操作。

newDirectory.then(function(result){
    getAllFiles('/TestDir').then((result) => {
       ...
    });
}).then(() => {
    console.log('doing some other task');
});

在上面的例子中,newDirectory Promise 会 resolve,然后 getAllFiles 会被调用,before getAllFiles 已完成执行,最后的 console.log 将被记录。这是 Promises 的一个重要原则,如果你希望它们同步运行,你需要链接 Promise,即你需要 return 通过所有链接的 then 函数的承诺。因此,要解决上述问题,我们需要 return 从 getAllFiles 函数解析的承诺,如下所示

newDirectory.then(function(result){
    return getAllFiles('/TestDir').then((result) => {
       ...
    });
}).then(() => {
    console.log('doing some other task');
});