简体   繁体   English

Node.js 在递归循环中返回一个 promise

[英]Node.js return a promise in recursive loop

I am having trouble getting a loop to return nested promises in the correct order.我无法让循​​环以正确的顺序返回嵌套的 Promise。 I am trying to recursively loop through a directory with subdirectories to get files and copy them to a new directory.我试图递归地遍历一个包含子目录的目录以获取文件并将它们复制到一个新目录。 This functionality is happening inside a Promise chain since other processes need to complete in order after the files are copied.此功能发生在 Promise 链中,因为其他进程需要在复制文件后按顺序完成。

The following code works but when I console.log out a string after the .then() it is logging before the promise has resolved.下面的代码有效,但是当我在.then()之后 console.log out 一个字符串时.then()它在承诺解决之前记录。

The code is:代码是:

    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
    });
        

The recursive function I'm calling "getAllFiles" to go through "TestDir" and its sub folders and copy files to "ExampleDir"我正在调用“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);
  });
}

The helper that copies the files returns a promise after the file has been copied复制文件的助手在复制文件后返回一个承诺

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

This actually seems to work but I am concerned it wont work with a large set of data since the这实际上似乎有效,但我担心它不适用于大量数据,因为

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

fires before the other logs.在其他日志之前触发。 The console looks like:控制台看起来像:

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

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?这是一个预期的日志,还是应该在记录“'这应该在上面的“结果”之后触发'”行之前解决并记录所有承诺?

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?这是一个预期的日志,还是应该在记录“'这应该在上面的“结果”之后触发'”行之前解决并记录所有承诺?

Yes, it is to be expected.是的,这是可以预料的。 You are writing your code as if it is synchronous when Promises are not meant for that.Promises不适用时,您编写的代码就好像它是同步的一样。 What is actually happening in the code snippet below is after the newDirectory Promise resolves, both the getAllFiles and console.log('this should fire after the "result" from above');下面的代码片段中实际发生的事情是在newDirectory Promise 解析之后, getAllFilesconsole.log('this should fire after the "result" from above'); getAllFiles console.log('this should fire after the "result" from above'); function will be executed immediately.函数将立即执行。 That is, the console.log will not wait on the getAllFiles to resolve before executing.也就是说,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
});

So if you wanted to change the order of the console.log to ensure that it is executed after the getAllFiles Promise resolves, you could rewrite as follows.所以如果你想改变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');
    });
});

You should also notice that I am saying when the Promise resolves, not when the function has finished executing.您还应该注意到,我说的是 Promise 何时解析,而不是函数完成执行时。 There is a very important distinction.有一个非常重要的区别。 If we take your example above yet again and and say we wanted to perform some other action after all the files had been copied.如果我们再次以上面的示例为例,并说我们想在复制所有文件后执行一些其他操作。

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

In the above example, what would happen is the newDirectory Promise would resolve, then the getAllFiles would be invoked, and before the getAllFiles has finished executing, the final console.log would be logged.在上面的例子中,会发生什么是newDirectory Promise 将解析,然后getAllFiles将被调用,并且getAllFiles完成执行之前,最终的 console.log 将被记录。 This is an important principal of Promises, if you wish them to behave synchronously, you need to chain the Promises, ie you need to return the promise through all the chained then functions.这是 Promises 的一个重要原则,如果你希望它们同步运行,你需要链接 Promises,即你需要通过所有链接的 then 函数返回承诺。 So to fix the above problem we would need to return the promise that is resolved from the getAllFiles function as follows因此,要解决上述问题,我们需要返回从getAllFiles函数解析的承诺,如下所示

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

fs/promises and fs.Dirent fs/promises 和 fs.Dirent

Here's an efficient, non-blocking ls program using Node's fast fs.Dirent objects and fs/promises module.这是一个使用 Node 的快速fs.Dirent对象和fs/promises模块的高效、非阻塞ls程序。 This approach allows you to skip wasteful fs.exist or fs.stat calls on every path.这种方法允许您跳过每条路径上浪费的fs.existfs.stat调用。

Using async and await , we can avoid having to think much about how to specifically wire up the Promises -使用asyncawait ,我们可以避免过多考虑如何专门连接 Promises -

// 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)

Let's get some sample files so we can see ls working -让我们获取一些示例文件,以便我们可以看到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'
]

See this related Q&A for a dir program that recursively lists directories, a search program for finding files, and more.有关递归列出目录的dir程序、用于查找文件的search程序等,请参阅此相关问答

If you want to console log after a Promise , you'll have to do it in a .then() like如果你想在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));

This is because Promise s are non-blocking and anything after it won't wait for it to finish before executing.这是因为Promise是非阻塞的,它之后的任何东西都不会等待它完成后再执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM