This is a continuation of the redux-Saga Mom tutorial (part 1). The last article triggered the word limit, so we started the Redux-Saga Mom Tutorial (Part 2).

7. Redux-saga provides cancellation, concurrent and race methods (Cancel, All, race)

Cancel method

When a task is cancelled by Cancel, cancel gives the currently cancelled task a chance to process the cancellation logic, finally.

  • The following is an example of the cancel method code:

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { put, take, call, delay, fork, cancel, all, race } from 'redux-saga/effects'
    
    function* forkTask() {
        try {
            // 2.1, delay 2s console prints 'forkTask finished'
            yield delay(2000)
            console.log('forkTask finished');
        } catch (error) {
            // 2.2, the console prints 'error' if there is an error
            console.log('error');
        } finally {
            // 2.3, forkTask will execute the finally block internals no matter how it ends
            console.log('Finnally executes when the task ends in any way, whether it ends normally, ends incorrectly, or ends when the current task is canceled.'); }}function* cancelFork() {
        // 2, cancelFork uses fork to start a non-blocking task forkTask
        const task = yield fork(forkTask)
        // 3, delay 1s
        yield delay(1000)
        // 4, disable forkTask
        yield cancel(task)
    }
    
    function* rootSaga() {
        // 1, root saga starts cancelFork
        yield call(cancelFork)
    }
    
    export default rootSaga
    
    Copy the code
    • First, root Saga starts the cancelFork task

    • After the cancelFork task is started, a non-blocking forkTask is first started with a fork. The task forkTask is delayed for 2 seconds before printing ‘forkTask Finished ‘.

    • Back in the cancelFork task, since the yield fork(forkTask) is non-blocking, the following code, yield delay(1000), blocks the cancelFork task for 1s

    • After 1s, before the forkTask has finished running (the forkTask is blocked for 2 seconds), the cancelFork task continues to execute the next yield Cancel (task) to cancel the forkTask

    • At this point, the forkTask is cancelled, and the code in the try is not finished because of the forkTask delay of 2 seconds, so console.log(‘forkTask Finished ‘) is not executed. For cancel, however, it terminates the forkTask, but it also gives the forkTask a place to handle cancellation logic in the finally block, so the console.log in the finally block (‘ When the task terminates in any way, either normally or incorrectly, Or if the current task is cancelled and ends, Finnally will perform ‘); Will perform.

    • So, when we cancel a task using the cancel method, if the task still has some cancellation logic, we can do it in finally.

All methods

The all method, similar to promise. all, provides concurrent tasks. When all tasks are complete, the Generator function will resume execution. If one of the parameters fails and the task does not handle the error, the error will bubble to the Generator where all is located and the other tasks will be cancelled.

All can block or non-block, depending on the form of the create Effect in all. If a task is created using a non-blocking method in the all argument, then all will not block the code following all, such as yield all(call(task1),fork(task2)). If all is yield all(fork(task1),fork(task2)), then all will not be blocked.

  • The code example for the all method is as follows: Task1 reject, task1 catch, task1 reject, task1 reject, task1 catch, task1 reject, Task1 reject

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { call, delay, all } from 'redux-saga/effects'
    
    function* task1() {
        try {
            Yield a failed Promise. This is equivalent to throwing an error of 1000
            yield Promise.reject('1000')
            // 1.2, the console will not output 'task1', because 1.1 has already thrown an error, so this code will not be executed
            console.log('task1');
        } catch (error) {
            // 1.3, catch catches the error thrown by yield promise.reject ('1000'), so here the console will print 'task1_error, 1000'
            console.log('task1_error', error)
        } finally {
            // 1.4, task1 will print 'task1_finally' because the throw is about to end and the finally block will be executed before it ends.
            console.log('2: task1_finally');
            // 1.5, task1 returns a 'task1 finished' string in finally
            return 'task1 finished'}}function* task2() {
        try {
            // 1.6, task2 task delay 2s
            yield delay(2000)
            // 1, 2 seconds after the console output 'task2'
            console.log('3: task2');
            // task2 returns the string 'task2 success', but the finally block finally contains a return statement
            / / but the console. The log (' ` task2 success ` '); It is still executed, but the return result is replaced by the return result in finally
            return console.log('4: task2 success');
        } catch (error) {
            Since task2 does not throw, the catch will not execute, so the following code will not execute
            console.log('1, task2_error', error)
        } finally {
            // 1.91, task completes normally, finally block executed, so 'task2_finally' is printed
            console.log('5: task2_finally');
            // 1.92, the finally block returns the string 'task2 finished' because a return in finally has more weight than a return in catch
            // So the final return value is the value returned in the finally block
            return 'task2 finished'}}function* rootSaga() {
        // 1, root saga uses all to concurrently start blocking tasks task1 and task2
        const res = yield all([call(task1), call(task2)])
        // 2, output the result when all receives the result
        console.log('6: res:, res);
    }
    
    export default rootSaga
    
    
    Copy the code
    • Here is a GIF of the console output effect for the all code sample above:

Race method

The race method is similar to promise.race, which is a multi-task race in the race parameter. The race ends if the first one completes it. There are also two cases: 1. If the first to complete the task completes normally, the other unfinished tasks will be cancelled, and the return value of the task when the task is completed, and the results of other cancelled tasks will be undefined. 2. If the task fails to be completed first (throwing errors and not processed), the error will bubble into the Generator function where race is located, and the tasks in other races will be cancelled.

  • In this example, task1 is reject, but it is processed by the error catch in Task1, so it is regarded as complete. Therefore, this code demonstrates the scenario where all tasks in race parameters are completed. The specific code analysis is in the order of comments.

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { call, delay, race } from 'redux-saga/effects'
    
    function* task1() {
        try {
            Yield a failed Promise. This is equivalent to throwing an error of 1000
            yield Promise.reject('1000')
            // 1.2, the console outputs 'task1', since 1.1 has already thrown an error, this code will not be executed
            console.log('task1');
        } catch (error) {
            // 1.3, catch catches the error thrown by yield promise.reject ('1000'), so here the console will print 'task1_error, 1000'
            console.log('1: task1_error', error)
        } finally {
            // 1.4, task1 will print 'task1_finally' because the throw is about to end and the finally block will be executed before it ends.
            console.log('2: task1_finally');
            // 1.5, task1 returns a 'task1 finished' string in finally
            return 'task1 finished'}}function* task2() {
        try {
            // 1.6, task2 is delayed for 2s, task1 is obviously faster than Task2, so task2 will be cancelled
            // So no code is executed until finally
            yield delay(2000)
            console.log('task2');
        } catch (error) {
            console.log('task2_error', error)
        } finally {
            // 1.7, task2 task cancelled, finally block executed, so 'task2_finally' will be printed
            console.log('3: task2_finally');
            // 1.8, because the task2 task was cancelled, its return result is not returned
            return 'task2 finished'}}function* rootSaga() {
        // 1, root Saga uses race to start blocking tasks task1 and Task2
        const res = yield race([call(task1), call(task2)])
        // 2, when race receives the result, the result will be undefined with or without a return because task2 has been cancelled
        ["task1 finished", undefined]
        console.log('4: res:, res);
    }
    
    export default rootSaga
    
    Copy the code
    • Here is the example Race code console output GIF above:

Cancel all race

  • cancel(… Parameters in the tasks) : cancel the tasks is optional, if pass, can pass in one or more task, like this cancel (task) | | cancel (task1, task2,… Tasks), where tasks are returned by the fork directive, which is used to unfork the corresponding tasks. If you want to perform some cancellation logic when the fork task is canceled, you can put the cancellation logic in the finally block.

    • Cancel If no arguments are received, yield cancel() will cancel the task on which the code is assigned, i.e., self-cancelTask.

      function* cancelTask() {
          try {
              // 1, cancel the current task cancelTask
              yield cancel()
              // 2, since the current task was cancelled, console.log(' Can I still be executed? '); Will not be implemented
              console.log('Can I still be executed? ');
          } catch (error) {
              console.log('error,cancelTask');
          } finally {
              // 3, the finally block will execute at the end of the task, so the following code will execute
              console.log('I'm sure I can be executed.'); }}Copy the code
  • Race ([…effects]) : Creates an Effect description, and the command middleware races among tasks. There are two cases: 1. If the first one completes the task normally, the other unfinished tasks will be cancelled, and the return value of the task will be returned when the task is completed, and the results of other cancelled tasks will be undefined. 2. If the task fails to be completed first (throwing errors and not processed), the error will bubble into the Generator function where race is located, and the tasks in other races will be cancelled. The preceding race demonstration is the successful scenario of the first completed task, that is, situation 1. The following demonstration is the failure scenario of the first completed task (Situation 2). The code is as follows.

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { call, delay, all, race } from 'redux-saga/effects'
    
    function* task1() {
        Yield a failed Promise. This is equivalent to reject a 1000 error
        yield Promise.reject('1000')
        // 1.2, since 1.1 has rejected an error, this code will not be executed, and task1 does not handle the error, which will bubble to the parent function
        console.log('task1');
    }
    function* task2() {
        // 1.3, task2 will be cancelled because task1 failed and the error was not handled, console.log('task2') follows; Will not be enforced
        yield delay(2000)
        console.log('task2');
    }
    
    function* rootSaga() {
        try {
            // 1, root Saga uses race to start blocking tasks task1 and Task2
            const res = yield race([call(task1), call(task2)])
            // 2, the result is output when race receives the result, but since the error in Task1 was not caught, the error bubbles into rootSaga, so the following code will not execute
            console.log('6: res:, res);
        } catch (error) {
            // 3, rootSaga's catch catches Task1 and prints the result
            console.log('1: Error in Task1 bubbling up to rootSaga, now caught by rootSaga '); }}export default rootSaga
    
    Copy the code
    • The above code console outputs the GIF:

  • Race (Effects) : Unlike Race ([… Effects]), effects is an effect collection in the form of an object. Its parameter form and output result are as follows code.

    function* raceTask() {
        // 1,task1 returns 1000, task2 returns 2000, task3 returns 3000
        const res = yield race({
            task1: call(task1),
            task2: call(task2),
            task3: call(task2)
        })
        //
        / / 2, which task2 first to finish, so res result will be {task1: undefined, task2:2000, task3: undefined}
        console.log(res);
    }
    Copy the code
  • All ([…effects]) : Create an Effect message, command middleware runs multiple effects in parallel, and waits for them all to complete. There are two situations: 1. 2. If a task in all fails, other tasks in all are cancelled and errors bubble to the Generator where all resides.

    • The previous all demonstration example belongs to case 1, that is, all tasks in ALL are completed. Case 2 will be demonstrated in code as follows. The code is as follows.

      / / the current path: / SRC/LearningSaga saga/index js
      
      import { call, delay, all } from 'redux-saga/effects'
      
      function* task1() {
          Yield a failed Promise. This is equivalent to reject a 1000 error
          yield Promise.reject('1000')
          // 1.2, since 1.1 has rejected an error, this code will not be executed, and task1 does not handle the error, which will bubble to the parent function
          console.log('task1');
      }
      function* task2() {
          try {
              // 1.3, because task1 failed and the error was not handled, the task2 task will be cancelled and the code in the try will not execute
              yield delay(2000)
              console.log('task2');
          } catch (error) {
      
          } finally {
              // 1.4, task2 is cancelled, finally is executed, so output 5: task2_finally
              console.log('1: task2_finally'); }}function* rootSaga() {
          try {
              // 1, root saga uses all to concurrently start blocking tasks task1 and task2
              const res = yield all([call(task1), call(task2)])
              // 2, when all receives the result, the result is printed, but since the error in Task1 was not caught, the error bubbles to rootSaga, so the following code will not execute
              console.log('6: res:, res);
          } catch (error) {
              // 3, rootSaga's catch catches Task1 and prints the result
              console.log('2: Error in Task1 bubbling up to rootSaga, now caught by rootSaga '); }}export default rootSaga
      
      Copy the code
    • The above code console outputs the GIF:

  • All (effects) : Different from all([…effects]), effects is an effect set in the form of an object. Its parameter form and output result are as follows code.

      function* allTask() {
        // 1,task1 returns 1000, task2 returns 2000, task3 returns 3000
        const res = yield all({
            task1: call(task1),
            task2: call(task2),
            task3: call(task2)
        })
        / / 2, res result will be {task1:1000, task2:2000, task3:3000}
        console.log(res);
      }
    Copy the code

8. Finally, we introduce a very common and simple redux-saga method select

  • Select is a method provided by Redux-Saga, which also creates an Effect by ordering the middleware to retrieve state data from store (store in Redux). Select will be demonstrated in code below:

  • First look at the content of our Reducer file, the code is as follows. After redux initialization, the state data should look like this: State :{reducer1:{name:’reducer1′},reducer2:{name:’reducer2′}}.

    / / the current path: SRC/LearningSaga/reducer/index. Js
    
    import { combineReducers } from 'redux'
    
    function reducer1(state = { name: 'reducer1' }, action) {
        return state
    }
    
    function reducer2(state = { name: 'reducer2' }, action) {
        return state
    }
    
    const rootReducer = combineReducers({ reducer1, reducer2 })
    
    export default rootReducer
    Copy the code
  • Continue with our Saga file

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { select } from 'redux-saga/effects'
    
    function* rootSaga() {
        // 1, use select to get state information in store
        const state = yield select()
        // 2, output state information
        console.log(state); 
    }
    
    export default rootSaga
    
    Copy the code
    • Const state = yield select(); const state = yield select(); const state = yield select(); All state data will be output, as shown in the screenshot below.

  • If we want to get some data in State, for example, I only want to get the reducer2 data, we can pass a selector function to select to tell select which part of the data we want to get in State. Here we use the reducer2 data as an example, the code is as follows:

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { select } from 'redux-saga/effects'
    
    function* rootSaga() {
        // 1, select the reducer2 data in state by using the selector function state => state.reducer2
        const reducer2 = yield select(state= > state.reducer2)
        // 2, output reducer2 data
        console.log(reducer2);
    }
    
    export default rootSaga
    Copy the code
    • In select, pass a function state => state.reducer2 that returns yield SELECT (…). In this function, state is injected by Redux-saga, which is equivalent to redux-Saga calling redux’s getState() method to get the data in state, and giving the data to our selector function, so that we can choose the data in the state arbitrarily. Here we need the reducer2 data, so we return the state.reducer2 data, and the console output is as follows:

    • Yield select(); yield select(state=>state);

  • Select (selector,… If args is present, the selector argument is equivalent to [state]. Concat (args). If args exists, the selector argument is equivalent to [state].

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { select } from 'redux-saga/effects'
    
    function* rootSaga() {
        const state = yield select(
             (state, ... args) = > { console.log('select all parameters except the selector are here: ', args); return state }
              , 1.2.3
        )
        console.log(state);
    }
    
    export default rootSaga
    Copy the code
    • The output of the saga running console is as follows:

9, epilogue

So far, the common methods of Redux-saga have been introduced with examples one by one. If readers really read it completely, this article should be enough for you to start using Redux-saga. Some uncommon methods of Redux-saga and others who want to learn about Redux-saga, You can read the official redux-Saga documentation. In fact, this article is basically written according to the official documentation, along with some small examples of your own. In addition, if you have any suggestions or exchanges, please leave a message and I will reply as soon as possible.