What's the difference between fork and spawn in redux-saga?
Asked Answered
B

2

36

The docs say fork is an attached fork and spawn is a detached fork - how do they differ?

Blowpipe answered 6/4, 2017 at 15:20 Comment(1)
Did you read the rest of the page?Toffic
S
37

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.

Stutter answered 8/4, 2017 at 4:2 Comment(4)
A 'spawned' process though will not block the parent This is also the case for forked processes, is it not?Tommyetommyrot
@Tommyetommyrot check also my answer above.Brogdon
The docs posted in the question directly contradict you: redux-saga.js.org/docs/advanced/ForkModel.html Scroll down the page to the example. Both fork and spawn are non-blocking. For fork the parent task will receive exceptions asynchronously from the child.Carlie
I think you are a bit confused between terminating the function, a.k.a returning and blocking the flow of it. fork DOES NOT block the function, meaning using for will have the function immediately yield the next task. But the function will NOT terminate until all forks are finished. With spawn - the function will terminate when it yields the spawned tasks, which will terminate on their own, kind of "disconnected" from their parent.Tucker
B
37

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.

Brogdon answered 2/1, 2018 at 2:7 Comment(5)
This is very useful thank you! Does 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
@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 exampleBrogdon
Thanks very much Grigoryan for sharing that info and so much for that example! I dont have anything but redux-saga installed, so does this mean I have nothing else in the async layers of dispatch?Whiteley
@Whiteley if you have no custom middleware and just redux and redux-saga, and taking into account the example behaviour, then definitely - YES : )Brogdon
Great explanation, just if anyone wants to try out and see the difference between fork and spawn in a sandbox: codesandbox.io/s/redux-saga-fork-spawn-test-forked-q00ogc?file=/…Arrow

© 2022 - 2024 — McMap. All rights reserved.