Сохранить вывод 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 асинхронной потоковой передачи и, по-видимому, задает как минимум три вещи.

  1. Как я могу получить output содержать массив массивов, представляющих проанализированные данные CSV?

Тот output никогда не будет существовать на верхнем уровне, как вы (и многие другие программисты) надеетесь, что это будет, из-за того, как работают асинхронные API. Все данные, собранные аккуратно в одном месте, могут существовать только в функции обратного вызова. Следующая лучшая вещь синтаксически const output = await somePromiseOfOutput() но это может произойти только в async function и только если мы переключимся с потоков на обещания. Это все возможно, и я упоминаю об этом, чтобы вы могли проверить это позже самостоятельно. Я предполагаю, что вы хотите придерживаться потоков.

Массив, состоящий из всех строк, может существовать только после чтения всего потока. Вот почему все строки доступны только в авторском примере "Stream API" только в .on('end', ...) Перезвоните. Если вы хотите что-то сделать со всеми присутствующими строками одновременно, вам нужно будет сделать это в конце обратного вызова.

Из https://csv.js.org/parse/api/ обратите внимание, что автор:

  1. использует обратный вызов on для чтения, чтобы вставить отдельные записи в ранее пустой массив, определенный с внешним именем output,
  2. использует обратный вызов при сообщении об ошибках
  3. использует обратный вызов 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' ] ] ) })

  1. Что касается цели взаимодействия с 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 там ничего полезного не делает. Конечно, как вы видели, данные не помещаются в выходную переменную.


  1. Что касается почему 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 });
    });

  });
}
Другие вопросы по тегам