I have the CSV reader class that takes two parameters (filePath, and model). The file path is the path to the.csv file, and the model is the model that the.csv will be built on. The data will be stored in the output array. It as has a problem as when I return the array, it is empty. But when I console log the array, it has the data in there. Can someone help me?
Index.js
const parse = require('csv-parse')
const fs = require('fs');
const output = [];
class CSVReader{
static GetRecord(filePath, model){
fs.createReadStream(filePath)
.pipe(parse({columns: false, delimiter: ',', trim: true, skip_empty_lines: true}))
.on('readable', function (){
let record
while (record = this.read()){
let city = model.create(record)
output.push(record)
}
})
.on('end', function (){
//console.log(output);
})
return output;
}
}
module.exports = CSVReader;
I used Jest to test the file but it has a problem as expected is 6, but I received []
Index.test.js
const CSVReader = require('../src/Index');
const City = require('../src/Models/City')
test('Can Read CSV File', () => {
let filePath = 'data/worldcities.csv';
let records = CSVReader.GetRecord(filePath, City);
expect(records.length).toBe(6);
});
This is an async call, so you cannot just return the response and expect it to work. You need to use the Promise API and an async function.
All of the Node.js fs
API is async (excluding the fs.*Sync
functions).
You can return a Promise
at the top of your function, then pass a callback:
return new Promise((resolve, reject) => { /* callback */ });
// all of this is fine
const parse = require('csv-parse')
const fs = require('fs');
// remove the const output = []; as this will cause problems
class CSVReader{
// this needs to be async
static async GetRecord(filePath, model){
// return a promise
return new Promise((resolve, reject) => {
// assign output here (https://stackoverflow.com/a/66402114/14133230)
const output = [];
fs.createReadStream(filePath)
.pipe(parse({columns: false, delimiter: ',', trim: true, skip_empty_lines: true}))
.on('readable', function (){
let record
while (record = this.read()){
let city = model.create(record)
output.push(record)
}
// you may need to call WriteStream#close() here
})
.on('end', function (){
// output is available here
// resolve the promise
resolve(output);
})
});
}
}
module.exports = CSVReader;
const CSVReader = require('../src/Index');
const City = require('../src/Models/City')
test('Can Read CSV File', () => {
let filePath = 'data/worldcities.csv';
let records = CSVReader.GetRecord(filePath, City); // type Promise
records.then((response) => { // when promise fills
expect(response.length).toBe(6);
});
});
First recommendation: get rid of the globally defined "const output = []" in your library class. This will cause so much confusion to whoever uses your static method. You'll end up accumulating results of previous calls on there.
Also I recommend: if you don't need it to handle huge csv files you can definitely go for the synchronous version and get rid of the complexity of the async loading.
const parse = require('csv-parse/lib/sync');
const fs = require('fs');
class CSVReader{
static GetRecord(filePath, model) {
const csvContents = fs.readFileSync(filePath);
const output = parse(csvContents, {columns: false, delimiter: ',', trim: true, skip_empty_lines: true})
.map(function (record) {
return model.create(record);
});
return output;
}
}
module.exports = CSVReader;
Another potentially helpful restructuring could come from attaching the factory pattern (which you currently aim to implement with CSVReader) directly to the base implementation of your Models. This will make it clearer which Models are usable with the CSVReader and how the implementation of a Model is structured to be usable with the pattern.
The base CSVModel
provides the general listFromCSV
functionality and modelFromRecord
instantiation.
src/Models/CSVModel.js
const parse = require('csv-parse/lib/sync');
const fs = require('fs');
class CSVModel {
static modelFromRecord(record) {
var model = new this();
model.record = record;
return model;
}
static listFromCSV (filePath) {
const csvContents = fs.readFileSync(filePath);
return parse(csvContents, {columns: false, delimiter: ',', trim: true, skip_empty_lines: true})
.map((record) => {
return this.modelFromRecord(record);
});
}
}
module.exports = CSVModel;
City
model inherits the listFromCSV
factory from the CSVModel and specifies its data association to local model properties
src/Models/City.js
const CSVModel = require('./CSVModel');
class City extends CSVModel {
static modelFromRecord(record) {
var model = super.modelFromRecord(record);
// assign properties of the CSV row to dedicated model properties
[model.name, model.id, model.asd] = [record[0], record[1], record[2]];
// or classical
model.name = record[0];
// ...
return model;
}
}
module.exports = City;
This allows the usage of the concrete Model Class to instantiate a list from a CSV.
test/csvmodel.test.js
const City = require('../src/Models/City');
test('Can Read City list', () => {
let filePath = 'data/worldcities.csv';
let records = City.listFromCSV(filePath);
expect(records.length).toBe(6);
});
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.