我遇到了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()
,这是我在日志中得到的:
您将在图像中看到,第一个迭代器的值成功返回readerResult
。第二个迭代器的值返回redux saga call
。在最后一个迭代器之前(即生成器完成时),我登录了response
,与最后一个迭代器一样,返回未定义。
传递给call
,request
的函数可与其他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);
}
答案 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());
我想您尝试手动迭代生成器函数,因为您无法在事件处理程序中使用yield
。 redux-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);