乐闻世界logo
搜索文章和话题

When to use fork in redux saga

3个答案

1
2
3

The fork effect in redux-saga is a non-blocking effect used to create a new saga branch that can run concurrently with the parent saga. Common scenarios for using fork include the following:

  1. Concurrent Task Execution: When you want to start a new task without blocking the current flow, you can use fork. This allows multiple tasks to execute simultaneously.

    Example: In a user login process, if you need to fetch data from multiple sources in parallel, such as user information, user settings, and user messages, you can use fork to start three different saga instances, which will run concurrently without waiting for each other.

    javascript
    function* loginFlow() { // ... login logic yield fork(fetchUserInfo); yield fork(fetchUserSettings); yield fork(fetchUserMessages); // ... other logic }
  2. Non-critical Tasks: If some tasks are secondary or their completion does not affect the continuation of the main flow, you can use fork to execute them.

    Example: After submitting form data, you might want to record some statistics, but you don't want the failure of the statistics code to affect the main flow.

    javascript
    function* submitFormSaga(data) { try { yield call(api.submitForm, data); // main task yield put({ type: 'FORM_SUBMIT_SUCCESS' }); yield fork(recordFormSubmitStats, data); // non-critical task } catch (e) { yield put({ type: 'FORM_SUBMIT_FAILURE', message: e.message }); } }
  3. Long-running Listeners: fork can be used to start a task that runs long-term and listens for future actions. It acts as a background task, continuously listening for certain actions without blocking other saga.

    Example: A chat application might need a saga to listen for RECEIVE_MESSAGE actions.

    javascript
    function* watchNewMessages() { while (true) { const action = yield take('RECEIVE_MESSAGE'); // handle received message } } function* mainSaga() { // ... yield fork(watchNewMessages); // ... }

When using fork, it's important to note that tasks created by fork do not block the continuation of the parent saga. If you need to ensure that a task completes before proceeding, you should use the call effect. Additionally, errors from fork-created tasks do not propagate to the parent saga, meaning they may silently fail in the background if not handled. Therefore, when starting fork tasks, it's typically necessary to handle errors appropriately within the task.

2024年6月29日 12:07 回复

The fork effect is particularly useful when dealing with scenarios involving multiple API call dispatches, as you can cancel these requests by canceling the task instance, for example, cancel(task1);.

It is particularly useful when the end user forcibly exits the application or when one of the tasks fails, causing issues with your instructions, strategies, and logic, and canceling or terminating the current processing task in the saga is reasonable.

Two methods can be used to cancel tasks.

Based on the non-blocking effect cancellation documentation from Redux Saga:

javascript
import { take, put, call, fork, cancel } from 'redux-saga/workers'; // ... function* loginFlow() { while (true) { const {user, password} = yield take('LOGIN_REQUEST') // Non-Blocking Effect which is the fork const task = yield fork(authorize, user, password) const action = yield take(['LOGOUT', 'LOGIN_ERROR']) if (action.type === 'LOGOUT'){ //cancel the task yield cancel(task) yield call(Api.clearItem, 'token') } } }

Alternatively,

javascript
import {call, put, fork, delay} from 'redux-saga/workers'; import someAction from 'action/someAction'; function* fetchAll() { yield fork(fetcher, 'users'); yield fork(fetcher, 'posts'); yield fork(fetcher, 'comments'); yield delay(1500); } function* fetcher(endpoint) { const res = yield call(fetchAPI, endpoint); if (!res.status) { throw new Error(`Error: ${res.error}`); } yield put(someAction({payload: res.payload})); } function* worker() { try { yield call(fetchAll); } catch (err) { // handle fetchAll errors } } function* watcher() { yield takeEvery(BLOGS.PUSH, worker); }

No problem! :)

2024年6月29日 12:07 回复

Generally, fork is useful when a saga needs to start a non-blocking task. Here, non-blocking means that the caller initiates the task and continues execution without waiting for it to complete.

In various scenarios, this feature is very useful, but there are mainly two:

  • Grouping sagas by logical domains
  • Retaining a reference to the task to allow canceling or joining it

Your top-level saga can be an example of the first use case. You might encounter something like:

javascript
yield fork(authSaga); yield fork(myDomainSpecificSaga); // you could use here something like yield []; // but it wouldn't make any difference here

Where authSaga might include:

javascript
yield takeEvery(USER_REQUESTED_LOGIN, authenticateUser); yield takeEvery(USER_REQUESTED_LOGOUT, logoutUser);

You can see that this example is equivalent to what you suggested, i.e., calling the fork saga and producing takeEvery calls. However, you only do this for code organization purposes. Since takeEvery itself is a forked task, it is generally useless redundancy.

An example of the second use case is:

javascript
yield take(USER_WAS_AUTHENTICATED); const task = yield fork(monitorUserProfileUpdates); yield take(USER_SIGNED_OUT); yield cancel(task);

You can see in this example that monitorUserProfileUpdates is executed by the calling saga when it resumes, and it waits for the USER_SIGNED_OUT action to be dispatched. It also retains a reference to it to allow canceling it when needed.

For completeness, there is another way to start non-blocking calls: spawn. The difference between fork and spawn lies in how errors and cancellations propagate from the child saga to the parent saga.

2024年6月29日 12:07 回复

你的答案