收益调用(...)未调用函数

时间:2018-10-05 21:12:32

标签: reactjs redux redux-saga side-effects

我遇到了redux-saga api call方法的问题,以及它是否按预期运行。我认为问题的症结在于call没有调用传递给它的函数。

这是启动api调用的主要生成器函数sendEmail

/**
 * A POST api call that will send a sendGrid email with csv data as an attachment
 *
 * @param  {object} action object containing the csv data, security key string, fields of CSV, and CSV file name
 *
 */

export function* sendEmail(action) {
  const { payload, security_key, CSVFields, CSVFileName } = action;
  //  API url
  const requestURL = `/api/email?security_key=${security_key}`;
  //  JSON2csvParser options, with the CSV fields
  const opts = { fields: CSVFields };
  //  The CSV data, which is a string
  const CSVData = payload;

  try {
    const parser = new Json2csvParser(opts);
    const csv = parser.parse(CSVData);
    //  create blob with the csv string
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });

    //  instantiate File Reader Web API
    const reader = new FileReader();

    //  when reader finishes reader.readAsDataURL(blob), stepthrough the emailAPI generator
    reader.addEventListener(
      'load',
      () => {
        const gen = emailAPI(requestURL, CSVFileName, reader);
        console.log("gen.next(): ", gen.next());
        console.log("gen.next(): ", gen.next());
        console.log("gen.next(): ", gen.next());
      },
      false,
    );

    //  reader reads the contents of the blob
    reader.readAsDataURL(blob);

    //  error handling of the reader
    reader.onerror = function (error) {
      console.log('Error: ', error);
    };

  } catch (err) {
    console.error(
      'sendEmail: Error occurred while parsing JSON to CSV ',
      err,
    );
  }
}

此生成器函数sendEmail将调用另一个生成器函数emailAPI

这是该生成器函数emailAPI的代码:

function* emailAPI(url, filename, reader) {
  let readerResult = null
  yield readerResult = reader.result.split(',')[1]
  const requestBody = {
    filename,
    content: readerResult,
  };

  try {
    const response = yield call(request, url, {
      method: 'POST',
      body: JSON.stringify(requestBody),
      headers: new Headers({
        'Content-Type': 'application/json',
        Accept: 'application/json',
      }),
    });
    console.log("response: ", response);
  } catch (err) {
    console.log("err: ", err);
    yield err
  }
}

逐步进入gen.next(),这是我在日志中得到的:

console logs of <code>gen.next()</code>

您将在图像中看到,第一个迭代器的值成功返回readerResult。第二个迭代器的值返回redux saga call。在最后一个迭代器之前(即生成器完成时),我登录了response,与最后一个迭代器一样,返回未定义

传递给callrequest的函数可与其他sagas一起使用。我正在request函数中登录以检查是否正在调用它。 不是。这是我所不期望的行为,有人知道为什么没有呼叫request吗?

编辑 这是request函数。仅fetch被传递了URL和选项。

import 'whatwg-fetch';

/**
 * Parses the JSON returned by a network request
 *
 * @param  {object} response A response from a network request
 *
 * @return {object}          The parsed JSON from the request
 */
function parseJSON(response) {
  if (response.status === 204 || response.status === 205) {
    return null;
  }
  return response.json();
}

/**
 * Checks if a network request came back fine, and throws an error if not
 *
 * @param  {object} response   A response from a network request
 *
 * @return {object|undefined} Returns either the response, or throws an error
 */
function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 *
 * @return {object}           The response data
 */
export default function request(url, options) {
  console.log("request    url: ", url);
  return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON);
}

2 个答案:

答案 0 :(得分:0)

问题

问题出在以下几行:

const gen = emailAPI(requestURL, CSVFileName, reader);
console.log("gen.next(): ", gen.next());
console.log("gen.next(): ", gen.next());
console.log("gen.next(): ", gen.next());

我想您尝试手动迭代生成器函数,因为您无法在事件处理程序中使用yieldredux-saga针对这种情况提供了eventChannel

首先,让我解释一下您为什么得到undefined以及为什么从未致电request的原因。这是因为yield call(request)仅返回需要由saga中间件处理的效果描述符。您在此处执行的操作只会遍历生成器,并且不会处理调用效果。同样在第三个gen.next()中,您没有传递任何内容(等于undefined)作为yield的返回值。这就是您在undefined控制台行中获得的response: undefined

解决方案

这不是一个完整的解决方案。我希望这会引导您朝正确的方向发展。如果需要,请在评论中进行澄清。

我已经稍微修改了您的传奇代码以使用事件渠道。您将需要对其进行改进以使其完全符合您的要求。

...
import { eventChannel } from 'redux-saga';
...

let fileReaderEventChannel;

export function getFileReaderEventChannel(blob) {
  if (fileReaderEventChannel) {
    fileReaderEventChannel = eventChannel((emit) => {

      //  instantiate File Reader Web API
      const reader = new FileReader();

      //  when reader finishes reader.readAsDataURL(blob), stepthrough the emailAPI generator
      reader.addEventListener(
        'load',
        () => {
          emit({ error: null })
        },
        false,
      );

      //  reader reads the contents of the blob
      reader.readAsDataURL(blob);

      //  error handling of the reader
      reader.onerror = function (error) {
        emit({ error });
      };

      return () => { 
        // Properly close or abort FileReader here.
      };

    });
  }
  return fileReaderEventChannel;
};

/**
 * A POST api call that will send a sendGrid email with csv data as an attachment
 *
 * @param  {object} action object containing the csv data, security key string, fields of CSV, and CSV file name
 *
 */

export function* sendEmail(action) {
  const { payload, security_key, CSVFields, CSVFileName } = action;
  //  API url
  const requestURL = `/api/email?security_key=${security_key}`;
  //  JSON2csvParser options, with the CSV fields
  const opts = { fields: CSVFields };
  //  The CSV data, which is a string
  const CSVData = payload;

  try {
    const parser = new Json2csvParser(opts);
    const csv = parser.parse(CSVData);
    //  create blob with the csv string
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });

    // CHECK HERE - START
    const channel = yield call(getFileReaderEventChannel, blob);
    const fileReaderEvent = yield take(channel);
    if (fileReaderEvent.error === null) {
      yield call(emailAPI, requestURL, CSVFileName, reader);
    } else {
      console.log('error', error);
    }
    // CHECK HERE - END

  } catch (err) {
    console.error(
      'sendEmail: Error occurred while parsing JSON to CSV ',
      err,
    );
  }
}

看看CHECK HERE注释和新功能getFileReaderEventChannel之间的代码。

参考:

答案 1 :(得分:0)

基本上,问题是不幸的是,您不能在生成器函数的回调中使用yield call(fn,arguments)(在这种情况下为sendEmail),因为回调通常是常规的函数,而不是生成器。

相反,您可以在生成器函数(eventChannel)的闭包内创建sendEmail并将eventChannel.emit作为回调传递。然后,您可以在eventChannel收听,您可以在其中使用使用yield call(fn,arguments)

更简单的用例将更加清晰 https://redux-saga.js.org/docs/advanced/Channels.html

<!-- language: lang-js -->
import { eventChannel, delay } from "redux-saga";

function* sendEmail(action) {
  const { payload, security_key, CSVFields, CSVFileName } = action;

  //  JSON2csvParser options, with the CSV fields
  const opts = { fields: CSVFields };
  //  The CSV data, which is a string
  const CSVData = payload;

  try {
    const parser = new Json2csvParser(opts);
    const csv = parser.parse(CSVData);
    //  create blob with the csv string
    const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
    //  instantiate File Reader Web API
    const reader = new FileReader();

    const loadChannel = eventChannel(emit => {
      reader.addEventListener("load", emit, false);
      const unsubscribeFn = () => reader.removeEventListener("load");
      return unsubscribeFn;
    });
    yield spawn(onReaderLoad, loadChannel);
    //  reader reads the contents of the blob
    reader.readAsDataURL(blob);
    //  error handling of the reader
    reader.onerror = console.log;
  } catch (err) {
    console.error("sendEmail: Error occurred while parsing JSON to CSV ", err);
  }
}

function* onReaderLoad(channel) {
  while (true) {
    const event = yield take(channel);
    const content = event.target.result.split(",")[1];
    const requestURL = `/api/email?security_key=${security_key}`;
    const requestBody = {
      CSVFileName,
      content
    };

    try {
      const response = yield call(request, requestURL, {
        method: "POST",
        body: JSON.stringify(requestBody),
        headers: new Headers({
          "Content-Type": "application/json",
          Accept: "application/json"
        })
      });
      console.log("response: ", response);
    } catch (err) {
      console.log("err: ", err);
      yield err;
    }
  }
}

我知道您仍然需要将CSVFileName传递给onReaderLoad生成器,但是我认为这对您来说是一个不错的练习。

我已通过emit作为回调 reader.addEventListener("load", emit, false); 但是相反,我们可以通过一个匿名fn并传递任何其他参数 reader.addEventListener("load", (event)=> emit {CSVFileName, event}, false);