【问题标题】:Writing files in a loop - JS循环写入文件 - JS
【发布时间】:2018-02-07 10:30:18
【问题描述】:

尝试在循环中写入文件时,我似乎遇到了问题,即使第一个文件尚未创建,循环仍在迭代(我想我要么不理解承诺,要么不理解脚本的异步性质)

所以我将在命令行上运行node write_file_script.js premier_league

// teams.js
module.exports = {
premier_league: [
  { team_name: "Team 1", friendly_name: "name 1"},
  { team_name: "Team 2", friendly_name: "name 2"}
 ]
}

我的脚本

const args = process.argv.slice(2);
const TEAM = require('./teams');

const Excel = require('exceljs');
const workbook = new Excel.Workbook();

for (var team = 0; team < TEAM[args].length; team++) {
  console.log("Starting Excel File generation for " + TEAM[args][team]['team_name']);
  var fhcw = require('../data_files/home/fhcw/' + TEAM[args][team]['friendly_name'] + '_home' + '.json');
  fhcw = fhcw.map(Number);

  workbook.xlsx.readFile('./excel_files/blank.xlsx')
  .then(function() {
    var worksheet = workbook.getWorksheet(1);
    // Write FHCW
    for (i=0; i < fhcw.length; i++) {
      col = i+6;
      worksheet.getCell('E'+ col).value = fhcw[i];
    }

    console.log(TEAM[args][team])
    workbook.xlsx.writeFile('./excel_files/' + TEAM[args] + '/' + TEAM[args][team]['friendly_name'] + '.xlsx');

  });

}

运行时我得到的输出是

Starting Excel File generation for Team 1
Starting Excel File generation for Team 2
undefined
(node:75393) UnhandledPromiseRejectionWarning: Unhandled promise rejection 
(rejection id: 1): TypeError: Cannot read property 'friendly_name' of undefined

所以看起来文件没有被写入但循环仍在继续,我如何确保在进入下一个循环之前写入文件?

谢谢

【问题讨论】:

标签: javascript node.js loops


【解决方案1】:

如果函数返回一个Promise,并且您想要串行(一次一个)而不是并行(同时启动),您需要在开始之前等待每个下一个使用then()

另外,请注意,您的 TEAM 只是一个数组的导出(至少按照所示),因此您不能给它 args,这是另一个错误的来源。

当您有一系列要做的事情时,最好的方法是建立一个队列,直到文件用完为止。在这种情况下,您的 TEAM 数组看起来就是您的队列,但由于这是一个导出,我建议您不必更改它,而是将其复制到另一个您可以更改的数组:

const args = process.argv.slice(2);
const TEAM = require('./teams');

const Excel = require('exceljs');
const workbook = new Excel.Workbook();

const writeNextFile = (queue) => {
  // If we have nothing left in the queue, we're done.
  if (!queue.length) {
    return Promise.resolve();
  }

  const team = queue.shift(); // get the first element, take it out of the array

  console.log("Starting Excel File generation for " + team.team_name);
  var fhcw = require('../data_files/home/fhcw/' + team.friendly_name + '_home' + '.json');
  fhcw = fhcw.map(Number);

  // return this promise chain
  return workbook.xlsx.readFile('./excel_files/blank.xlsx')
  .then(function() {
    var worksheet = workbook.getWorksheet(1);
    // Write FHCW
    for (i=0; i < fhcw.length; i++) {
      col = i+6;
      worksheet.getCell('E'+ col).value = fhcw[i];
    }

    console.log(team);
    // not sure what you thought this would TEAM[args] would have translated to, but it wouldn't have been a string, so I put ??? for now
    // also, making the assumption that writeFile() returns a Promise.
    return workbook.xlsx.writeFile('./excel_files/' + team.??? + '/' + team.friendly_name + '.xlsx');
  }).then(() => writeNextFile(queue));
}

writeNextFile(TEAM.slice(0)) // make a copy of TEAM so we can alter it
  .then(() => console.log('Done'))
  .catch(err => console.error('There was an error', err)); 

基本上,我们的函数接受一个数组并写入第一个团队,然后递归调用自身以调用下一个团队。最终一切都会解决,你最终会得到一个最终解决的 Promise。

说到 Promises,你基本上总是需要将它们链接在一起。您将无法为它们使用 for 循环,或任何其他标准循环。

如果您想一次将它们全部写出来,它会更简洁一些,因为您可以为每个都做一个非递归映射,然后使用Promise.all 知道它们何时完成。

const writeNextFile = (team) => {
  console.log("Starting Excel File generation for " + team.team_name);
  var fhcw = require('../data_files/home/fhcw/' + team.friendly_name + '_home' + '.json');
  fhcw = fhcw.map(Number);

  // return this promise chain
  return workbook.xlsx.readFile('./excel_files/blank.xlsx')
  .then(function() {
    var worksheet = workbook.getWorksheet(1);
    // Write FHCW
    for (i=0; i < fhcw.length; i++) {
      col = i+6;
      worksheet.getCell('E'+ col).value = fhcw[i];
    }

    console.log(team);
    // not sure what you thought this would TEAM[args] would have translated to, but it wouldn't have been a string, so I put ??? for now
    // also, making the assumption that writeFile() returns a Promise.
    return workbook.xlsx.writeFile('./excel_files/' + team.??? + '/' + team.friendly_name + '.xlsx');
  });
}

Promise.all(TEAM.map(writeTeamFile))
  .then(() => console.log('Done')
  .catch(err => console.error('Error'));

Promises 用于异步,通常当您有一组事情时,您希望并行执行它们,因为它更快。这就是为什么并行版本更干净的原因。我们不必递归调用。

【讨论】:

  • 我对你的Also, note that your TEAM is just an export of an array (at least as presented), so you can't give it args, which is where the other error comes from. 声明有疑问,如果我运行node script.js premier_league,那么我的论点是premier_league,这让我可以访问TEAM 中的那个对象,出于某种原因,我认为它会翻译成字符串premier_league 但这显然是不正确的
  • 哦,对不起,我误解了你的目的。是的,如果args 是 'premier_league, then you are correct that you should use args` 那里......有点。而不是process.argv.slice(2),只需执行process.argv[2],否则您将拥有一个没有意义的数组。但是,我所做的teams.??? 仍然没有意义。在这种情况下,您可能只需要args 而不是teams[args]
  • 好吧,我刚刚将 league: 键添加到 JSON 对象,所以可以引用 team.league.. 所以你的答案有效(串行版本),稍加修改 const writeNextFile = function(queue),得到了没有错误。
  • 哈哈,没问题,非常感谢您花时间回答的这么详细:-),希望我能更了解这些事情
【解决方案2】:

由于您没有写入同一个文件,因此您可以遍历文件并执行读/写操作。

您可以在循环中绑定索引值,然后继续前进。

示例代码。

var filesWrittenCount = 0;

for(var team = 0; team < TEAM[args].length; team++){
    (function(t){
        // call your async function here. 
        workbook.xlsx.readFile('./excel_files/blank.xlsx').then(function() {
            var worksheet = workbook.getWorksheet(1);
            // Write FHCW
            for (i=0; i < fhcw.length; i++) {
                col = i+6;
                worksheet.getCell('E'+ col).value = fhcw[i];
            }

            console.log(TEAM[args][t])
            workbook.xlsx.writeFile('./excel_files/' + TEAM[args] + '/' + TEAM[args][t]['friendly_name'] + '.xlsx');

            // update number of files written.
            filesWrittenCount++;

            if (totalFiles == filesWrittenCount) {
                // final callback function - This states that all files are updated
                done();
            }

        });
    }(team));
}

function done() {
    console.log('All data has been loaded');
}

希望这会有所帮助。

【讨论】:

  • writeFile() 也返回一个 Promise,所以它必须在其中。看起来他们也想一次写一个文件。如果是这种情况,这将不会这样做。它同时将它们全部关闭,有更清洁的方法。
  • 如果他们正在编写不同的文件,我看不出序列化它们的理由。他们可能有一个用例。
  • 我也没有,但他们的问题是关于让一个在另一个之前完成。即使您要使用 Promise 进行并行化,您也应该使用 Promise.all 而不是计数器和回调。
  • 感谢两位的回答,我将通过它们并尝试获得一些理解。我不确定目前如果我并行或串行执行它们会有所不同,因为我没有很多文件要创建,但是并行增加的越多,显然会有它的好处
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-02
  • 2012-06-27
  • 2014-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多