[英]Yield call(…) not invoking function
I am running into issues with the redux-saga
api call
method and whether it is behaving as intended. 我
redux-saga
了关于redux-saga
api call
方法以及它是否按预期方式运行的问题。 The crux of the issue I believe is that the call
isn't calling the function passed to it. 我认为,问题的症结在于
call
没有调用传递给它的函数。
Here is the main generator function sendEmail
that starts the api 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,
);
}
}
This generator function sendEmail
will call another generator function emailAPI
. 这个生成器函数
sendEmail
将调用另一个生成器函数emailAPI
。
This is the code for that generator function 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
}
}
Stepping through with gen.next()
, this what I get in the logs: gen.next()
,这是我在日志中得到的:
As you'll see in the image, the value of the first iterator returns readerResult
successfully. 正如您将在图像中看到的,第一个迭代器的值成功返回了
readerResult
。 The value of the second iterator returns the redux saga call
. 第二个迭代器的值返回redux saga
call
。 Before the last iterator (which is when the generator is done) I log response
, which returns undefined as does the last iterator. 在最后一个迭代器之前(即生成器完成时),我记录了
response
, 它与最后一个迭代器一样返回undefined 。
The function passed to call
, request
, does work with other sagas. 传递给
call
, request
的函数与其他sagas一起使用。 I am logging within the request
function to check if it is being called. 我在
request
函数中记录日志,以检查是否正在调用它。 It is not . 不是 。 This is behaviour I am not expecting, does anyone know why
request
isn't being called? 这是我所不期望的行为,有人知道为什么没有调用
request
吗?
EDIT Here is the request
function. 编辑这是
request
功能。 Just a fetch
being passed the url and options. 只是
fetch
传递的网址和选项。
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);
}
The Problem 问题
The problem is in these lines: 问题出在以下几行:
const gen = emailAPI(requestURL, CSVFileName, reader);
console.log("gen.next(): ", gen.next());
console.log("gen.next(): ", gen.next());
console.log("gen.next(): ", gen.next());
I suppose you tried to iterate the generator function manually since you couldn't use yield
inside the event handler. 我想您尝试手动迭代生成器函数,因为您无法在事件处理程序中使用
yield
。 redux-saga
offers eventChannel
for situations like this. redux-saga
为eventChannel
情况提供了eventChannel
。
First, let me explain why you got undefined
and why request
was never called. 首先,让我解释一下为什么您
undefined
以及为什么从未调用request
。 That is because, yield call(request)
will only return an effect descriptor which needs to be handled by the saga middleware. 这是因为
yield call(request)
将仅返回需要由saga中间件处理的效果描述符。 What you're doing here only iterates through the generator, and does nothing to handle the call effect. 您在此处执行的操作只会遍历生成器,并且不会处理调用效果。 Also in the third
gen.next()
, you're passing nothing (equivalent to undefined
) as the yield
's return value. 同样在第三个
gen.next()
,您没有传递任何内容(等于undefined
)作为yield
的返回值。 That's the undefined
you got in response: undefined
console line. 那就是您得到的
undefined
response: undefined
控制台行。
Solution 解
This is not a complete solution. 这不是一个完整的解决方案。 I hope this will guide you in the right direction.
我希望这会引导您朝正确的方向发展。 Please ask for clarification in comments if you need.
如果需要,请在评论中进行澄清。
I have slightly modified your saga code to use event channels. 我已经稍微修改了您的传奇代码以使用事件通道。 You will need to improve it to exactly match your requirement.
您将需要对其进行改进以使其完全符合您的要求。
...
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,
);
}
}
Have a look at the code between CHECK HERE
comments and the new function getFileReaderEventChannel
. 看一下
CHECK HERE
注释和新函数getFileReaderEventChannel
之间的代码。
References: 参考文献:
Basically the problem is unfortunately you can not use yield call(fn,arguments)
within a callback in a generator function ( sendEmail
in this case) as the callback is expected to be a regular function, not a generator one. 基本上,问题是不幸的是,您不能在生成器函数(在本例中为
sendEmail
yield call(fn,arguments)
的回调中使用yield call(fn,arguments)
,因为该回调应该是常规函数,而不是生成器函数。
Instead you create a eventChannel
within the closure of the generator function ( sendEmail
) and pass eventChannel.emit
as the callback. 相反,您可以在生成器函数(
sendEmail
)的闭包内创建一个eventChannel
,并将eventChannel.emit
作为回调传递。 Then you listen into that eventChannel
where you can use yield call(fn,arguments)
然后,侦听该
eventChannel
,在其中可以使用yield call(fn,arguments)
A simpler use case would be clearer https://redux-saga.js.org/docs/advanced/Channels.html 一个更简单的用例将更加清晰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;
}
}
}
I am aware that you still need to pass CSVFileName
to the onReaderLoad
generator but I think it would be a nice exercise for you. 我知道您仍然需要将
CSVFileName
传递给onReaderLoad
生成器,但是我认为这对您来说是一个不错的练习。
I have passed emit
as the callback reader.addEventListener("load", emit, false);
我已经通过
emit
作为回调reader.addEventListener("load", emit, false);
but instead we could well have passed an anonymous fn and pass any extra arguments reader.addEventListener("load", (event)=> emit {CSVFileName, event}, false);
但相反,我们可以传递一个匿名fn并传递任何额外的参数
reader.addEventListener("load", (event)=> emit {CSVFileName, event}, false);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.