Сохранить вывод csv-parse в переменную
Я новичок в использовании csv-parse, и этот пример из github проекта делает то, что мне нужно, с одним исключением. Вместо вывода через console.log я хочу хранить данные в переменной. Я попытался присвоить строку fs переменной, а затем вернуть data
вместо того, чтобы регистрировать это, но это только вернуло целую кучу вещей, которые я не понимал. Конечная цель - импортировать файл CSV в SQLite.
var fs = require('fs');
var parse = require('..');
var parser = parse({delimiter: ';'}, function(err, data){
console.log(data);
});
fs.createReadStream(__dirname+'/fs_read.csv').pipe(parser);
Вот что я попробовал:
const fs = require("fs");
const parse = require("./node_modules/csv-parse");
const sqlite3 = require("sqlite3");
// const db = new sqlite3.Database("testing.sqlite");
let parser = parse({delimiter: ","}, (err, data) => {
// console.log(data);
return data;
});
const output = fs.createReadStream(__dirname + "/users.csv").pipe(parser);
console.log(output);
1 ответ
Это вопрос, который наводит на мысль о путанице в отношении API асинхронной потоковой передачи и, по-видимому, задает как минимум три вещи.
- Как я могу получить
output
содержать массив массивов, представляющих проанализированные данные CSV?
Тот output
никогда не будет существовать на верхнем уровне, как вы (и многие другие программисты) надеетесь, что это будет, из-за того, как работают асинхронные API. Все данные, собранные аккуратно в одном месте, могут существовать только в функции обратного вызова. Следующая лучшая вещь синтаксически const output = await somePromiseOfOutput()
но это может произойти только в async function
и только если мы переключимся с потоков на обещания. Это все возможно, и я упоминаю об этом, чтобы вы могли проверить это позже самостоятельно. Я предполагаю, что вы хотите придерживаться потоков.
Массив, состоящий из всех строк, может существовать только после чтения всего потока. Вот почему все строки доступны только в авторском примере "Stream API" только в .on('end', ...)
Перезвоните. Если вы хотите что-то сделать со всеми присутствующими строками одновременно, вам нужно будет сделать это в конце обратного вызова.
Из https://csv.js.org/parse/api/ обратите внимание, что автор:
- использует обратный вызов on для чтения, чтобы вставить отдельные записи в ранее пустой массив, определенный с внешним именем
output
, - использует обратный вызов при сообщении об ошибках
- использует обратный вызов on-end для сравнения всех накопленных записей в выходных данных с ожидаемым результатом
...
const output = []
...
parser.on('readable', function(){
let record
while (record = parser.read()) {
output.push(record)
}
})
// Catch any error
parser.on('error', function(err){
console.error(err.message)
})
// When we are done, test that the parsed output matched what expected
parser.on('end', function(){
assert.deepEqual(
output,
[
[ 'root','x','0','0','root','/root','/bin/bash' ],
[ 'someone','x','1022','1022','','/home/someone','/bin/bash' ]
]
)
})
- Что касается цели взаимодействия с sqlite, то это по сути создание настраиваемой конечной точки потоковой передачи.
В этом случае используйте настраиваемый поток записи, который принимает выходные данные синтаксического анализатора и отправляет строки в базу данных.
Тогда вы просто соединяете каналы как
fs.createReadStream(__dirname+'/fs_read.csv')
.pipe(parser)
.pipe(your_writable_stream)
Осторожно: этот код возвращается немедленно. Он не ждет окончания операций. Он взаимодействует со скрытым циклом событий, внутренним для node.js. Цикл событий часто сбивает с толку новых разработчиков, которые приходят с другого языка, привыкли к более императивному стилю и пропускают эту часть своего обучения node.js.
Реализация такого настраиваемого записываемого потока может быть сложной и оставлена в качестве упражнения для читателя. Это будет проще всего, если парсер выберет строку, и тогда средство записи может быть написано для обработки отдельных строк. Удостоверьтесь, что вы можете как-то замечать ошибки и выдавать соответствующие исключения, иначе вас будут ругать за неполные результаты, без предупреждений или причин, почему.
Хакерский способ сделать это был бы заменить console.log(data)
в let parser = ...
с индивидуальной функцией writeRowToSqlite(data)
что вам придется написать в любом случае для реализации пользовательского потока. Из-за асинхронных проблем API, использование return data
там ничего полезного не делает. Конечно, как вы видели, данные не помещаются в выходную переменную.
- Что касается почему
output
в ваших модифицированных постингах данные не содержатся...
К сожалению, как вы обнаружили, это обычно неправильно:
const output = fs.createReadStream(__dirname + "/users.csv").pipe(parser);
console.log(output);
Здесь переменная output
будет ReadableStream, который не совпадает с данными, содержащимися в читаемом потоке. Проще говоря, это как когда у вас есть файл в вашей файловой системе, и вы можете получить все виды системной информации о файле, но доступ к содержимому в файле осуществляется через другой вызов.
Я также изо всех сил пытался понять, как вернуть данные из csv-parse на верхний уровень, который вызывает синтаксический анализ. В частности, я пытался получить данные parser.info в конце обработки, чтобы узнать, была ли она успешной, но решение для этого может работать и для получения данных строки, если вам нужно.
Ключ состоял в том, чтобы обернуть всех прослушивателей событий потока в Promise, а в обратном вызове парсера разрешить Promise.
function startFileImport(myFile) {
// THIS IS THE WRAPPER YOU NEED
return new Promise((resolve, reject) => {
let readStream = fs.createReadStream(myFile);
let fileRows = [];
const parser = parse({
delimiter: ','
});
// Use the readable stream api
parser.on('readable', function () {
let record
while (record = parser.read()) {
if (record) { fileRows.push(record); }
}
});
// Catch any error
parser.on('error', function (err) {
console.error(err.message)
});
parser.on('end', function () {
const { lines } = parser.info;
// RESOLVE OUTPUT THAT YOU WANT AT PARENT-LEVEL
resolve({ status: 'Successfully processed lines: ', lines });
});
// This will wait until we know the readable stream is actually valid before piping
readStream.on('open', function () {
// This just pipes the read stream to the response object (which goes to the client)
readStream.pipe(parser);
});
// This catches any errors that happen while creating the readable stream (usually invalid names)
readStream.on('error', function (err) {
resolve({ status: null, error: 'readStream error' + err });
});
});
}