Async Operations in React Redux Applications
Key Points
- The single-threaded nature of JavaScript means that asynchronous operations, such as API calls, are critical to non-blocking UIs, and efficient handling of these operations is critical in React Redux applications.
- Redux Thunk, Redux-Saga, and Redux-Observables are popular Redux asynchronous operation management middleware, each providing different advantages based on the complexity and needs of the application.
- Redux Thunk simplifies asynchronous action distribution by allowing return functions instead of action, enabling sequential API calls and processing related data in these functions.
- Redux-Saga leverages ES6 generators to provide more powerful solutions for complex scenarios such as handling race conditions, pauses, and cancel operations, making it suitable for large applications.
- Redux-Observables leverages RxJS to provide a powerful way to manage side effects through action flows, providing excellent scalability and control over asynchronous operations in complex application architectures.
This article was originally published in Codebrahma.
JavaScript is a single-threaded programming language. That is to say, when you write the following code...
…The second line will only be executed after the first line is executed. This won't be a problem in most cases, because the client or server performs millions of calculations per second. We only notice these effects when we perform costly calculations (a task that takes quite a while to complete – a network request takes some time to return).
Why am I only showing API calls (network requests) here? What about other asynchronous operations? API calls are a very simple and useful example to describe how to handle asynchronous operations. There are other operations, such as setTimeout()
, performance-intensive computing, image loading, and any event-driven operations.
When building an application, we need to consider how asynchronous execution affects the structure. For example, think of fetch()
as a function that performs an API call (network request) from the browser. (Ignore whether it is an AJAX request. Just treat its behavior as asynchronous or synchronous.) The time elapsed when the request is processed on the server does not occur on the main thread. Therefore, your JS code will continue to execute and once the request returns a response, it will update the thread.
Consider this code:
userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
In this case, since fetch()
is asynchronous, when we try to get userDetails
we will not have userId
. So we need to build it in a way that ensures that the second row is executed only after the first row returns the response.
Most modern network request implementations are asynchronous. But this doesn't always work because we rely on previous API response data for subsequent API calls. Let's see how to build it specifically in a ReactJS/Redux application.
React is a front-end library for creating user interfaces. Redux is a state container that manages the entire state of an application. Using React with Redux allows us to create efficient and scalable applications. In such a React application, there are several ways to build asynchronous operations. For each method, we will discuss its advantages and disadvantages in terms of the following factors:
- Code clarity
- Scalability
- Easy to handle errors
For each method, we will execute these two API calls:
1. Get userDetails
city from
(first API response)
Suppose the endpoint is /details
. It will include the city in the response. The response will be an object:
userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
2. Based on the user city, we will get all restaurants in city
Suppose the endpoint is. The response will be an array: /restuarants/:city
userDetails: { … city: 'city', … };
- Use Promise or async/await directly with
setState
Using Redux Thunk- Using Redux-Saga
- Using Redux Observables
redux-async, redux-promise, redux-async-queue).
PromisesPromise is an object that may produce a single value at some time in the future: a parsed value or an unresolved cause (for example, a network error occurred). — Eric ElliotIn our example, we will use the axios library to get the data, which returns a promise when we make a network request. The Promise may parse and return a response or throw an error. So once the
React component is mounted, we can get it directly like this:
['restaurant1', 'restaurant2', …]
component will automatically re-render and load the restaurant list.
Async/await is a new implementation that we can use to perform asynchronous operations. For example, the same function can be achieved by:
componentDidMount() { axios.get('/details') // 获取用户详细信息 .then(response => { const userCity = response.city; axios.get(`/restaurants/${userCity}`) .then(restaurantResponse => { this.setState({ listOfRestaurants: restaurantResponse, // 设置状态 }) }) }) }
Disadvantages of this method
The problem arises when complex data-based interactions occur. For example, consider the following situation:
- We do not want the thread executing JS to be blocked by network requests.
- All the above situations make the code very complex and difficult to maintain and test.
- Also, scalability will be a big problem because if we plan to change the flow of the application, we need to remove all the fetch operations from the component.
- Imagine what we would do if the component was on top of the parent-child tree. Then we need to change all the representation components that depend on the data.
- Also note that the entire business logic is inside the component.
How do we improve?
-
Status Management Using global storage can actually solve half of our problems in these cases. We will use Redux as our global storage.
-
Move business logic to the right place If we consider moving business logic out of the component, where exactly can we do this? In actions? In reducers? Through middleware? Redux's architecture is synchronous. Once you distribute an action (JS object) and it reaches the storage, the reducer will operate on it.
-
Make sure there is a separate thread that executes the asynchronous code and that any changes to the global state can be retrieved by subscription
From this we can learn that if we move all the acquisition logic before the reducer—i.e., action or middleware—then we can distribute the correct action at the right time. For example, once the fetch starts, we can distribute dispatch({ type: 'FETCH_STARTED' })
, and when it is done, we can distribute dispatch({ type: 'FETCH_SUCCESS' })
.
Want to develop a React JS application?
Using Redux Thunk
Redux Thunk is a middleware for Redux. It basically allows us to return functions instead of objects as action. This helps by providing dispatch
and getState
as parameters of the function. We use dispatch
to distribute necessary actions at the right time. The benefits are:
- Allow multiple distributions within the function
- The association of business logic with the acquisition will be moved from the React component to the actions.
In our example, we can rewrite the action like this:
userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
As you can see, we now have a good control over what type of action is distributed. Each function call (such as fetchStarted()
, fetchUserDetailsSuccess()
, fetchRestaurantsSuccess()
, and fetchError()
) will distribute an action of type normal JavaScript object, and additional details can be added if needed. So now the task of reducer is to process each action and update the view. I didn't discuss reducer because it's simple from here and the implementation may be different.
To make this work, we need to connect the React component to Redux and bind the action to the component using the Redux library. Once done, we can simply call this.props.getRestaurants()
, which in turn will handle all the above tasks and update the view according to the reducer.
For its scalability, Redux Thunk can be used for applications that do not involve complex control of asynchronous actions. Additionally, it works seamlessly with other libraries, as described in the topic in the following section.
However, it is still a bit difficult to perform certain tasks using Redux Thunk. For example, we need to pause the intermediate fetch operation, or only allow the latest calls when there are multiple such calls, or if other APIs get this data and we need to cancel.
We can still implement these, but it will be more complicated to execute accurately. Compared with other libraries, the code clarity of complex tasks will be slightly worse and will be more difficult to maintain.
Using Redux-Saga
With the Redux-Saga middleware, we can gain the additional advantage to address most of the above features. Redux-Saga is developed based on the ES6 generator.
Redux-Saga provides an API that helps achieve the following goals:
- Blocking events, blocking threads on the same line until some operations are completed
- Non-blocking events, making the code asynchronous
- Handle competition between multiple asynchronous requests
- Pause/throttling/de-bounce any action
How does Saga work?
Saga uses a combination of ES6 generator and async/await API to simplify asynchronous operations. It basically does its work on its separate threads where we can make multiple API calls. We can use their API to make each call synchronous or asynchronous, depending on the use case. The API provides the ability to make a thread wait on the same line until the request returns a response. Apart from that, this library provides many other APIs, which make API requests very easy to handle.
Consider our previous example: If we initialize a saga and configure it with Redux based on what is mentioned in its documentation, we can do the following:
userDetails: { … city: 'city', … };
So if we distribute it using a simple action of type FETCH_RESTAURANTS
, the Saga middleware will listen and respond. In fact, no action is used by middleware. It just listens and performs some other tasks, and distributes new actions if needed. By using this architecture, we can distribute multiple requests, each of which describes:
- When will the first request start?
- When will the first request be completed
- When will the second request start?
and so on.
In addition, you can see the advantages of fetchRestaurantSaga()
. We are currently using the call
API to implement blocking calls. Saga provides other APIs, such as fork()
, which implements non-blocking calls. We can combine blocking and non-blocking calls to maintain a structure that is suitable for our application.
With scalability, using saga is beneficial:
- We can build and group saga based on any specific task. We can trigger a saga from another saga by simply distributing an action.
- Since it is a middleware, the action we wrote will be a normal JS object, unlike thunk.
- Since we put the business logic inside saga (which is a middleware), it would be much easier to understand the React part of it if we know what saga is capable of.
- Errors can be easily monitored and distributed to storage through try/catch mode.
Using Redux-Observables
As stated in its documentation, "Epic is the core primitive of redux-observable" section:
- Epic is a function that receives the action stream and returns the action stream. That is, Epic runs in parallel with the normal Redux distribution channel, after the reducer has received them.
- action is always run through a reducer before epic receives them. Epic receives and outputs only another action stream. This is similar to Redux-Saga, because none of the actions are used by middleware. It just listens and performs some other tasks.
For our task, we can simply write the following code:
userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
At first, this may seem a bit confusing. However, the more you understand RxJS, the easier it is to create an Epic.
Like saga, we can distribute multiple actions, each of which describes which part of the API request chain the thread is currently in.
In terms of scalability, we can split or combine Epic based on a specific task. Therefore, this library can help build scalable applications. If we understand the observable pattern of writing code, the code clarity is good.
My preferences
How to determine which library to use? It depends on how complex our API requests are.
How to choose between Redux-Saga and Redux-Observable? It depends on the learning generator or RxJS. Both are different concepts, but equally good enough. I suggest trying both to see which one is better for you.
Where is putting the business logic for processing APIs? It is best to put it before the reducer, but not in the component. The best way is in the middleware (using saga or observable).
You can read more React development articles at Codebrahma.
FAQs about asynchronous operations in React-Redux applications
What is the role of middleware in handling asynchronous operations in Redux?
Middleware in Redux plays a crucial role in handling asynchronous operations. It provides a third-party extension point between the distribution action and the action arrives at the reducer. Middleware can be used to record, modify, and even cancel actions, as well as to distribute other actions. In the context of asynchronous operations, middleware like Redux Thunk or Redux Saga allows you to write action creators that return functions instead of action . This function can then be used to delay the distribution of an action or to only distribute an action when a specific condition is met.
How does Redux Thunk help manage asynchronous operations?
Redux Thunk is a middleware that allows you to write action creators that return functions instead of action. thunk can be used to delay the distribution of actions or to distribute actions only when certain conditions are met. This feature makes it an excellent tool for handling asynchronous operations in Redux. For example, you can distribute an action to indicate the start of an API call, and then distribute another action when the call returns data or error message.
What is the difference between Redux Thunk and Redux Saga?
Redux Thunk and Redux Saga are both middleware for managing side effects in Redux, including asynchronous operations. The main difference between the two is their approach. Redux Thunk uses callback functions to handle asynchronous operations, while Redux Saga uses generator functions and more declarative methods. This makes Redux Saga more powerful and flexible, but also more complex. If your application has simple asynchronous operations, Redux Thunk may suffice. However, for more complex scenarios involving race conditions, cancellation, and if-else logic, Redux Saga may be a better choice.
How to handle errors in asynchronous operations in Redux?
Action can be distributed when an error occurs during an asynchronous operation to handle error handling in asynchronous operation in Redux. This action can take its error message as its payload. You can then handle this action in the reducer to update the status with the error message. This way, an error message can be displayed to the user or recorded for debugging.
How to test asynchronous action in Redux?
Asynchronous actions in Redux can be tested by mocking Redux storage and API calls. For Redux storage, you can use libraries like redux-mock-store
. For API calls, you can use libraries like fetch-mock
or nock
. In your tests, you can distribute an asynchronous action and then assert that the expected action has been distributed with the correct payload.
How to cancel asynchronous operations in Redux?
Middleware like Redux Saga can be used to cancel asynchronous operations in Redux. Redux Saga uses generator functions, which can be cancelled using cancel
effect. When cancel
effect is yielded, saga will be cancelled from its startup point until the current effect is cancelled.
How to handle race conditions in asynchronous operations in Redux?
Middleware like Redux Saga can be used to handle race conditions in asynchronous operations in Redux. Redux Saga provides effects like takeLatest
and takeEvery
that can be used to handle concurrent actions. For example, if the previously started saga task is still running when a new action is distributed, takeLatest
cancels the task.
How to use async/await with Redux Thunk?
Redux Thunk natively supports async/await. In your action creator, you can return asynchronous functions instead of regular functions. Inside this asynchronous function, you can use async/await to handle asynchronous operations. When the asynchronous operation is completed, the dispatch
function can be called using the action object.
How to handle loading state in asynchronous operations in Redux?
The loading state in asynchronous operations in Redux can be handled by distributing actions before and after asynchronous operations. The action distributed before the operation can set the load status to true, and the action distributed after the operation can set it to false. In your reducer, you can handle these actions to update the load state in the storage.
How to deal with side effects in Redux?
Side effects in Redux can be handled using middleware like Redux Thunk or Redux Saga. These middleware allows you to write action creators that return functions instead of action . This function can be used to perform side effects such as asynchronous operations, logging, and conditionally distributing actions.
The above is the detailed content of Async Operations in React Redux Applications. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.
