Await 函数不包含来自 Promise 的完整数据

Await function does not contain full data from Promise

我不明白为什么它没有像我预期的那样工作。


(async () => {
  try {
    const pages = await generateURLs();
    console.log(pages);
    console.log(await generateURLs());
  } catch (e) {
    console.error(e);
    return;
  }
})();

console.log(await generateURLS()) returns 从函数返回的完整数组。但是 const pages = await generateURLs() 只包含数组中的第一项。请帮我理解为什么?

函数 generateURLs() returns 如下所示的承诺。

function generateURLs() {
  let urls = [];
  const directories = [
    {
      name: 'components',
      path: 'build/components',
    },
    {
      name: 'patterns',
      path: 'build/patterns',
    },
    {
      name: 'styles',
      path: 'build/styles',
    },
  ];

  return new Promise((resolve, reject) => {
    for (const directory of directories) {
      fs.readdir(directory.path, (err, folders) => {
        if (err) {
          console.log('Unable to scan directory: ' + err);
          reject(err);
        } else {
          for (const folder of folders) {
            glob(`${directory.path}/${folder}/**/*.html`, { ignore: `${directory.path}/${folder}/index.html` }, (er, files) => {
              if (er) {
                console.log('Problem getting files paths: ' + er);
              } else {
                for (const file of files) {
                  urls.push(file);
                  resolve(urls);
                }
              }
            });
          }
        }
      });
    }
  });
}

fs.readDir 是异步的。

这意味着在最内层 for (const file of files) 循环的第一次迭代中解析 generateURLs 中的承诺 returned 允许调用者访问 urls 数组仅包含为 for( directory of directories) 循环的第一个目录推送的条目。

generateURLs 编辑的承诺 return 应该在最外层 for (const directory of directories) 循环内执行的所有操作都已完成并且 urls 数组包含要传递给调用者的所有条目。

但是,其中一些操作本身是异步的,即 fs.readdirglob return 通过回调函数的异步结果。

解决方案可能是使用 fs.readirSyncglob.sync 同步执行操作,或构建回调函数嵌套,或将操作转换为 return promises 并继续创建一个承诺数组并使用 Promise.all 等待它们被解析,或者在 async 函数中使用 await 运算符。

其中,使用 await 可能是最简单的编码方式,但它会按顺序而不是并行地启动文件系统请求(无论如何这可能是首选)。

以下示例概述了如何将 generateURLs 转换为 async 函数,return 是对 urls 数组的承诺。下面 await 抛出的任何错误都将简单地拒绝 generateURLs 所做出的 return 承诺。我将它展示为 CommonJS 模块,但主要是为了封装一些支持代码:

"use strict"; // generateURLs as a module

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

// Use promisify to convert callback methods to promise returning
const glob = util.promisify( require("glob"));
const readdir = util.promisify(fs.readdir)

async function generateURLs() {
  let urls = [];
  const directories = [
    {
      name: 'components',
      path: 'build/components',
    },
    {
      name: 'patterns',
      path: 'build/patterns',
    },
    {
      name: 'styles',
      path: 'build/styles',
    },
  ];

  for (const directory of directories) {
    const folders= await readdir(directory.path);
    for (const folder of folders) {
       const files = await glob(`${directory.path}/${folder}/**/*.html`, { ignore: `${directory.path}/${folder}/index.html` });
       for (const file of files) {
         urls.push(file);
       }
    }
  }
  return urls;
}
module.exports = generateURLs;

我没有尝试准确解释为什么发布的代码会产生错误的输出。根据我的经验,确保 returned 承诺在 urls 完全填充后得到解决比找出为什么过早解决它会产生它所产生的结果更有效率。