The docs say fork is an attached fork and spawn is a detached fork - how do they differ?
One way to look at it is to see your saga's as a Graph. 'fork' creates a child from the calling process. While 'spawn' creates a new child at the root of the graph.
So when you 'fork' another process, the parent process will wait until the 'forked' process is finished. Also every exception will bubble up from the child to the parent and can be caught in the parent.
A 'spawned' process though will not block the parent, so the next 'yield' statement is called immediately. Also the parent process will not be able to catch any exceptions that happen in the 'spawned' process.
I hope this was helpful.
A 'spawned' process though will not block the parent
This is also the case for fork
ed processes, is it not? –
Tommyetommyrot In the same docs it says:
When the parent terminates the execution of its own body of instructions, it will wait for all forked tasks to terminate before returning.
Let's say we have this setup where in the middle of execution flow we call fetchAll()
that might call either fork
or spawn
:
const { delay, runSaga } = require("redux-saga");
const fetch = require("node-fetch");
const { fork, spawn, call, put} = require("redux-saga/effects");
function* fetchResource(resource) {
console.log(`Fetch ${resource} start`);
const response = yield call(fetch, "https://jsonplaceholder.typicode.com" + resource);
const text = yield call(response.text.bind(response));
console.log(`Fetch ${resource} end`);
}
function* fetchAll() {
console.log("Fork or Spawn start");
// this is pseudo code, I mean here that you can use either
// fork or spawn, check results below
const task1 = yield fork||spawn(fetchResource, "/posts/1");
const task2 = yield fork||spawn(fetchResource, "/posts/2");
console.log("Fork or Spawn end");
}
function* main() {
console.log("Main start");
yield call(fetchAll);
console.log("Main end");
}
runSaga({}, main);
// RESULTS WITH FORK(): | RESULTS WITH SPAWN():
// |
// Main start | Main start
// Fork start | Spawn start
// Fetch /posts/1 start | Fetch /posts/1 start
// Fetch /posts/2 start | Fetch /posts/2 start
// Fork end | Spawn end
// Fetch /posts/2 end | Main end <--
// Fetch /posts/1 end | Fetch /posts/2 end
// Main end <-- | Fetch /posts/1 end
What we see is, within call
context the fork is non-blocking
, but call
is not going to finish until all its children processes are finalised, since call
itself is a blocking effect.
You wouldn't see the same if you call a fork
within another fork
, since fork itself is non-blocking, and inner forked processes would leak
out of outer fork processes, but will be kept inside the nearest blocking context. This is the essence of attachment to parent
.
So parent yield call(forkedProcess)
being of a blocking nature will await for return
or throw resolution
of child fork processes.
This is not the case with spawn()
, however, since spawn is detached from enclosing parent process, i.e. attached to the root process, thus local parent
doesn't have to wait.
Hope this clarifies it a bit.
put
also work like fork
? In that the parent waits till the put is done, so after every yield put
, the next yield select
is guranteed to give the put'ed value from store? –
Whiteley put
and select
delegate some control to redux to finalize the action, so it depends on the middleware stack you have on redux side, if there are no async layers in dispatching an action, then you can rely on the correct state withdrawal with select
after put
. But the select and put should be in the same process when being forked, otherwise select
and put
can get desynced due to different ways they are scheduled within redux-saga. Check this example –
Brogdon © 2022 - 2024 — McMap. All rights reserved.