简体   繁体   English

NodeJS CSVReader - 带有 csv-parse 的 createReadStream 返回空数组

[英]NodeJS CSVReader - createReadStream with csv-parse returns empty array

I have the CSV reader class that takes two parameters (filePath, and model).我有 CSV 阅读器 class,它采用两个参数(文件路径和模型)。 The file path is the path to the.csv file, and the model is the model that the.csv will be built on.文件路径为.csv文件的路径,model为model,即.Z628CB5675FF5288B3AF775FF528F3AFE7 The data will be stored in the output array.数据将存储在 output 阵列中。 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索引.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 []我用 Jest 测试了文件,但它有一个问题,正如预期的那样是 6,但我收到了 []

Index.test.js索引.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);
});

tl;dr: tl;博士:

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.您需要使用 Promise API 和异步 function。

What makes this async?是什么让这个异步?

All of the Node.js fs API is async (excluding the fs.*Sync functions).所有 Node.js fs API 都是异步的(不包括fs.*Sync功能)。

How can I use Promises?我如何使用 Promise?

You can return a Promise at the top of your function, then pass a callback:您可以在Promise的顶部返回 Promise,然后传递回调:

return new Promise((resolve, reject) => { /* callback */ });

Fixing the code修复代码

// 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;

Using the new Promise-based function使用基于 Promise 的新 function

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.第一个建议:删除库 class 中全局定义的“const output = []”。 This will cause so much confusion to whoever uses your static method.这会给使用您的 static 方法的人造成很大的困惑。 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.我还建议:如果您不需要它来处理巨大的 csv 文件,那么您绝对可以将 go 用于同步版本,并摆脱异步加载的复杂性。

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.另一个可能有用的重组可能来自将工厂模式(您目前打算使用 CSVReader 实现)直接附加到模型的基本实现。 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.这将更清楚哪些模型可与 CSVReader 一起使用,以及 Model 的实现如何构造为可用于该模式。

The base CSVModel provides the general listFromCSV functionality and modelFromRecord instantiation.基础CSVModel提供通用listFromCSV功能和modelFromRecord实例化。

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 City model 从 CSVModel 继承listFromCSV工厂,并指定其与本地 model 属性的数据关联

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.这允许使用具体的 Model Class 从 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);
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM