开发者问题收集

循环写入文件 - JS

2018-02-07
1057

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

因此,在命令行上,我将运行 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

因此,似乎文件未被写入但循环仍在继续,如何确保在进入下一个循环之前文件已被写入?

谢谢

2个回答

如果函数返回一个 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 用于异步,通常当您有一组操作时,您希望并行执行它们,因为这样更快。这就是为什么并行版本更简洁的原因;我们不必以递归方式调用事物。

samanime
2018-02-07

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

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

示例代码。

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

希望这会有所帮助。

krisnik
2018-02-07