30

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

PaulB
  • 20,984
  • 13
  • 54
  • 75

2 Answers2

31

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.

Oliver Watkins
  • 10,301
  • 19
  • 83
  • 168
Alex
  • 2,362
  • 1
  • 22
  • 26
  • 3
    `A 'spawned' process though will not block the parent` This is also the case for `fork`ed processes, is it not? – JohnnyQ Nov 01 '17 at 03:13
  • @JohnnyQ check also my answer above. – Karen Grigoryan Jan 02 '18 at 11:21
  • 1
    The docs posted in the question directly contradict you: https://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. – Charles Taylor Sep 21 '19 at 15:26
30

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.

Karen Grigoryan
  • 4,300
  • 2
  • 15
  • 29
  • 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? – Noitidart May 19 '18 at 19:36
  • 1
    @Noitidart `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](https://codesandbox.io/s/7k97w90xx?expanddevtools=1&hidenavigation=1&moduleview=1&view=editor) – Karen Grigoryan May 21 '18 at 14:09
  • 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? – Noitidart May 21 '18 at 16:23
  • 1
    @Noitidart if you have no custom middleware and just redux and redux-saga, and taking into account the example behaviour, then definitely - YES : ) – Karen Grigoryan May 21 '18 at 17:10