Search code examples
node.jscsvpromisefast-csv

Node.js: Return promises in a defined order


I have some hundreds of JSON files that I need to process in a defined sequence and write back the content as CSV in the same order as in the JSON files:

  1. Write a CSV file with header
  2. Collect an array of JSON files to process
  3. Read the file and return an array with the required information
  4. Append the CSV file, created under #1, with the information
  5. Continue with the next JSON file at step #3
'use strict';
const glob = require('glob');
const fs = require('fs');
const fastcsv = require('fast-csv');
const readFile = require('util').promisify(fs.readFile);

function writeHeader(fileName) {
  return new Promise((resolve, reject) => {
    fastcsv
      .writeToStream(fs.createWriteStream(fileName), [['id', 'aa', 'bb']], {headers: true})
      .on('error', (err) => reject(err))
      .on('finish', () => resolve(true));
  });
}

function buildFileList(globPattern) {
  return new Promise((resolve, reject) => {
    glob(globPattern, (err, files) => {
      if (err) {
        reject(err);
      } else {
        resolve(files);
      }
    });
  });
}

function readFromFile(file) {
  return new Promise((resolve, reject) => {
    readFile(file, 'utf8', (err, data) => {
      if (err) {
        reject(err);
      } else {
        const obj = JSON.parse(data);
        const key = Object.keys(obj['776'])[0];
        const solarValues = [];
        obj['776'][key].map((item, i) => solarValues.push([i, item[0], item[1][0][0]]));
        resolve(solarValues);
      }
    });
  });
}

function csvAppend(fileName, rows = []) {
  return new Promise((resolve, reject) => {
    const csvFile = fs.createWriteStream(fileName, {flags: 'a'});
    csvFile.write('\n');
    fastcsv
      .writeToStream(csvFile, rows, {headers: false})
      .on('error', (err) => reject(err))
      .on('finish', () => resolve(true));
  });
}

writeHeader('test.csv')
  .then(() => buildFileList('data/*.json'))
  .then(fileList => Promise.all(fileList.map(item => readFromFile(item))))
  .then(result => Promise.all(result.map(item => csvAppend('test.csv', item))))
  .catch(err => console.log(err.message));

JSON examples:

https://gist.github.com/Sineos/a40718c13ad0834b4a0056091e3ac4ca

https://gist.github.com/Sineos/d626c3087074c23a073379ecef84a55c

Question

While the code basically works, my problem is that the CSV is not written back in a defined order but mixed up like in an asynchronous process.

I tried various combinations with and without Promise.all resulting in either pending promises or mixed up CSV file.

This is my first take on Node.js Promises so every input on how to do it correctly is greatly appreciated. Many thanks in advance.


Solution

  • This code should process your files in order, we'll use async/await and for .. of to loop in sequence:

    async function processJsonFiles() {
        try {
            await writeHeader('test.csv');
            let fileList = await buildFileList('data/*.json');
            for(let file of fileList) {
                let rows = await readFromFile(file);
                await csvAppend('test.csv', rows);
            }
        } catch (err) {
            console.error(err.message);
        }
    }
    
    processJsonFiles();