What is Redux-Saga?
Redux-Saga is a library primarily aimed to make application side effects like asynchronous data fetching and accessing impure browser cache. It is very easy to manage and efficient to execute. With Redux-Saga, it is easy to test and handle failure effortlessly.
Redux-Saga can be thought of as the organized way of helping the application made with Redux to communicate and stay in sync with external dependencies like API. In general terms, it is a middleware library that can be used to make HTTP requests to external services, execute I/O operations, and access browsers’ storage. The power of Redux-Saga is to organize and manage the side effects in a way that becomes easier for the developer to manage.
In this tutorial, you will be introduced to Redux-Saga and everything associated with each from scratch. For your convenience, you are going to use a trivial counter from the Redux repository on GitHub. This will be easy to understand without having to worry about excessive details.
Setting up Redux-Saga
To get hands-on experience on Redux-Saga, you are required to set it up initially on your system. First of all, you need to clone the GitHub repository; the URL is given below.
After the given GitHub repository is cloned, use the below command and hit enter.
After the above-given step, the next step is to run the application, which is done by using the command given below.
As soon as the NPM starts the server, the application will start functioning. The application contains two basic use cases, i.e., two buttons for incrementing or decrementing a counter. In case of any issue, repeat the same command and proceed.
Now, you are going to create your first Saga. Considering that you are a beginner, you may traditionally create a simple “Hello World” version for Saga. If things go well, you can see two buttons along the message displaying the counter value is 0. To proceed with the application creation, you need to write some JavaScript functions and save them below.
The above function is nothing but returns and greets the “Hello Sagas!” message. Thus, run Saga, you need to create a middleware with a list of Sagas. In the above code, there’s only Saga. It would help if you created such multiple Sagas to connect with the middleware. Thus, your main.js folder should constitute the following changes.
In the above code snippet, you are first required to import Saga from the application found inside the ./sagas module. The next step you need to perform is to create a middleware using the function createSagaMiddleware by exporting it from the redux-saga library.
Before running the helloSaga, you must connect the middleware to the store using the method applyMiddleware. Then, to start the Saga, you will use sagaMiddleware.run(helloSaga) function.
Creating Asynchronous calls
Redux Saga uses the ES6 generator function for asynchronous calls. These generators allow the synchronously written code to be converted into asynchronous calls. Also, at each asynchronous call, the generator pauses the calls until the previous call is completed.
Let’s proceed by adding something to the counter. To illustrate and understand the asynchronous call, you must create another button that will increment the counter for 1 second every time you click. To provide that additional button, create a callback for the UI component using onIncrementAsync, as shown below.
After the above step, the next task is to connect the onIncremenAsync of the Component to a Store action. Thus, modify the main.js file as shown below.
It is important to note that, unlike redux-thunk, the Component you used dispatches only object action. So, you need to use another Saga that performs asynchronous calls. In this case, you need to start a task that will wait for 1 second before the counter is incremented. To do that, add the following code into your main.js file.
In the above code snippet, you created a delay function that returns a Promise. The Promise will resolve after some milliseconds. Then, you implemented the same function to block the generator.
To be precise, sagas are always treated as generator functions. Their main purpose is to generate objects for the redux-saga middleware. The generated objects are instructions used by the middleware to be interpreted. Thus, the Promise acts as an object to the middleware and suspends the Saga until the Promise completes. In the above code, the incremental sync Saga gets suspended the delay of the Promise is resolved precisely at 1 second.
Once the Promise is resolved, the Saga is resumed again by the middleware. Thus, in the above example, the next statement gives another object because it gets called from the ({type: ‘INCREMENT’}) and is instructed by the middleware for increment action when dispatched.
The next step is executed through when the Effect is called. Effects are just objects that contain middleware instructions. Whenever the middleware retrieves an Effect by a Saga, Saga automatically pauses until the requirements are fulfilled. Summarizing the code, the incremental sync Saga sleeps only for 1 second, increment action is dispatched. Next, another Saga watchIncrementAsync is created. The helper function takes every listens to the dispatched INCREMENT_ASYNC and runs incrementAsync each time.
Now, since two sagas have been created successfully, you need to start them together. To achieve that, you need to add rootSaga that starts all other Sagas. In the same file, copy the code snippet given below.
The above code creates an array due to calling both the Sagas together, i.e., helloSaga and watchIncrementAsync. This usually means the there will be two generators that will be running in parallel. It would be best to invoke the sagaMiddleware.run method from the rootSaga in the main.js file. It can be done using the following code snippet shown below.
As previously discussed, incrementAsync is a generator function. Whenever it runs, it gives out an iterators object, and the iterator’s next method returns another object something like this.
This object contains fields of the given expression, which is ultimately the indication that the generator has been terminated or there might be some more expressions being completed. In your case, the incremental sync generates two values with the help of the generator given below.
It can also be invoked using the next method of the generator. The result would be something like this.
For the first two invocations, the results of the yielded expression are generated. In the case of 3rd invocation, since only two Sagas were created, it gets no yield from the field even if it is set to true. Also, because the incremental sync Generator doesn’t return anything, so the file remains undefined.
After following all the above sets, you are set to test the logic prevailing inside the incrementAsync. You will need to iterate over the generator and return the value generator generated. It is done in the following way given below.
There’s one issue that might arise while testing. The return value of the delay is not ensured in the testing mechanism. You can’t use Promises to ensure normal values are entered. Thus, to solve this issue, Redux-Saga provides a method. The method involves the direct calling of delay(1000) function inside the incrementAsync to make subsequent deep comparison possible.
In the above code snippet, you could notice that instead of using delay(1000), the yield call(delay,1000) is being used. In the first case, the expression has a delay(1000) and it is evaluated before it gets passed to the next caller. The next caller can be called middleware when the code is running. Even it can test the code which runs the Generator function and return it. This is why the caller gets a Promise for the first case.
In the second scenario, notice that the expression call(delay,1000) has been used to get the caller of the next call. It simply returns the Effect which advices the middleware to call a function based on the argument provided. The only plain JavaScript object is returned because it neither puts nor performs any dispatches, nor even the asynchronous calls by itself. To summarize, the following results are returned.
This means that when middleware finds the yielded effects, it simply fulfills it. If there exists a PUT Effect, then there shall be another dispatch of another towards the Store. If there exist a CALL, the function shall be called. This fine line of separation between Effect creation and execution makes it easier for the Generator to test. The final code snippet after implementing the Effect looks something like this.
The call and put are just plain JavaScript objects that can be anytime used to the same function of the code snippet given above. Thus, to test the logic applied with the incrementAsync, you iterate over and over the generator to test out values using the deepEqual method. Since you are done with the concept of Redux-Saga and its practical implementation, let’s test it using the command shown below:
Glossary
So far we have discussed the overview of Redux-Saga, let’s see some of the core terms associated with it.
Effect
In Redux-Saga, and Effect is defined as a plain JavaScript object that contains some sort of instructions that are executed by the middleware of the Saga.
Effects can be created by the inclusion of factory functions that are provided by the Redux-Saga library. For instance, if you want to use the call(myfunction, ‘argument1’, ‘arguement2’) to provide instruction to the middleware to invoke the function, the generator executes this function and returns an Effect.
Task
A task can be defined as a process that runs in the background. In an application based out of Redux-Saga, there exist multiple processes that run behind the scene parallel. A task can be created by simply invoking the fork function example of which can be shown using the code snippet below.
Blocking/Non-blocking call
Blocking a call in Redux-Saga results in the creation of Effect and the same Effect waits for the outcome till the execution and blocks the next instruction that is waiting to be executed once the current scenario is successfully executed yielding a Generator.
On the other hand, non-blocking calls in Redux-Saga means the Saga will automatically get resumed after the Effect is yielded. An example for both the calls can be shown through the example given below.
Watcher/Worker
Watcher/ Worker refer to the proper way of organizing the flow control. In this process, two separate Sagas are used. The watcher serves the task of dispatching ambient actions along with the usage of the fork function for each action. On the other hand, the worker serves the tasks of handling all the actions and terminates. The application of worker/watcher can be shown using the following example.
Summary
In this tutorial, you learned about Redux-Saga and some of its most important factors that are crucial in the application and its overall development process. In the later sections, you saw how Effects and Generators play a typical role in defining the import aspects of Redux-Saga. Redux-Saga is originally a library that empowers running tasks in the background effectively so that the foreground process of the application development does not get exclusively affected.