Импорт XLSX-файла в приложение React с помощью redux-sagas

#reactjs #redux #xlsx #redux-saga

#reactjs #redux #xlsx #redux-saga

Вопрос:

Я новичок в redux-sagas и изо всех сил пытаюсь заставить приведенный ниже код работать.

Это сага, с которой я работаю, предполагается, что она считывает файл xlsx и отправляет действие по завершении. Действие должно быть отправлено ПОСЛЕ того, как файл будет полностью прочитан, но это не так, и я понятия не имею, что я делаю не так.

 import { put } from 'redux-saga/effects';
import * as XLSX from 'xlsx';

import * as actionTypes from '../actions/actionTypes';

export function* importFileSaga(action) {
  console.log('[importFileSaga]', action.fileToUpload);
  const response = yield importFile(action.fileToUpload);
  console.log('[importFileSaga]', response);
  yield put ({type: actionTypes.SET_DATA, payload: response});
}

const importFile = file2upload => {
  console.log('[importFile]', file2upload);
  const reader = new FileReader();
  reader.onload = (evt) => {
    /* Parse data */
    const bstr = evt.target.result;
    const wb = XLSX.read(bstr, { type: 'binary' });
    /* Get first worksheet */
    const wsname = wb.SheetNames[0];
    const ws = wb.Sheets[wsname];
    const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });

    const processedData = processData(data);
    console.log('[importFile]',processedData);

    return processedData;
  };
  reader.readAsBinaryString(file2upload);

}

// process CSV data
const processData = dataString => {
    const dataStringLines = dataString.split(/rn|n/);
    const headers = dataStringLines[0].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);

    const list = [];
    for (let i = 1; i < dataStringLines.length; i  ) {
      const row = dataStringLines[i].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
      if (headers amp;amp; row.length === headers.length) {
        const obj = {};
        for (let j = 0; j < headers.length; j  ) {
          let d = row[j];
          if (d.length > 0) {
            if (d[0] === '"')
              d = d.substring(1, d.length - 1);
            if (d[d.length - 1] === '"')
              d = d.substring(d.length - 2, 1);
          }
          if (headers[j]) {
            obj[headers[j]] = d;
          }
        }

        // remove the blank rows
        if (Object.values(obj).filter(x => x).length > 0) {
          list.push(obj);
        }
      }
    }

    // prepare columns list from headers
    const columns = headers.map(c => ({
      name: c,
      selector: c,
    }));

    const processedData = {header: columns, data: list};
    console.log('[processData]', processedData);
    return processedData;
}
  

Когда загружается новый файл, это последовательность вывода, которую я получаю в консоли

 [importFileSaga] 
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile] 
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[importFileSaga] undefined importFile.js:9
SET_DATA reducers.js:31
undefined
TypeError: action.payload is undefined
The above error occurred in task importFileSaga
    created by takeEvery(UPLOAD_FILE_START, importFileSaga)
    created by watchImport
Tasks cancelled due to error:
takeEvery(UPLOAD_FILE_START, importFileSaga)
takeEvery(TEST_SAGA_INIT, importDeviceSaga) index.js:1
[processData] 
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile] 
Object { header: (50) […], data: (2) […] }

  

Это неожиданно…но я предполагаю, что я все еще могу быть немного сбит с толку.
Я бы ожидал чего-то подобного

 [importFileSaga] 
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile] 
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[processData] 
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile] 
Object { header: (50) […], data: (2) […] }
[importFileSaga] some data here importFile.js:9
SET_DATA reducers.js:31
  

Я понимаю, что эта строка кода

const ответ = выход importFile(action.fileToUpload);

означает, что строка ниже не выполняется, пока не будет завершен importFile. Это правильно? Как я могу это исправить?

— РЕДАКТИРОВАТЬ —

Благодаря markerikson я изменил функцию importFile

 const importFile = file2upload => {
  return new Promise((resolve, reject) => {
    console.log('[importFile]', file2upload);
    const reader = new FileReader();
    reader.onload = (evt) => {
      /* Parse data */
      const bstr = evt.target.result;
      const wb = XLSX.read(bstr, { type: 'binary' });
      /* Get first worksheet */
      const wsname = wb.SheetNames[0];
      const ws = wb.Sheets[wsname];
      const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });

      const processedData = processData(data);
      console.log('[importFile]',processedData);

      //return processedData;
      resolve(processedData);
    };
    reader.readAsBinaryString(file2upload);
  });

}
  

и теперь это работает как шарм!!

Ответ №1:

Концептуально здесь две проблемы:

  • yield importFile() собирается перейти undefined к промежуточному программному обеспечению saga, которое, предположительно, будет продолжать работать
  • Программа чтения файлов не будет выполнять обратный вызов сразу, и вашему коду действительно нужно подождать, пока это не будет сделано.

Вам нужно будет реструктурировать код так, чтобы он ожидал, пока не будет выполнена логика чтения.

Одним из вариантов может быть ручное создание Promise внутри importFile и возврат его, а также разрешение обещания в конце .onload обратного вызова.