Redux-saga first recognized and used, redux-saga first recognized

Source: Internet
Author: User

Redux-saga first recognized and used, redux-saga first recognized

Redux-saga is a middleware that manages asynchronous operations of Redux applications. Its functions are similar to redux-thunk + async/await, it stores all asynchronous operation logic in one place for centralized processing by creating Sagas.

Redux-saga effects

Effects in redux-saga is a plain text JavaScript Object that contains some commands that will be executed by saga middleware. The operations executed by these commands include:

  1. Initiate an asynchronous call (for example, sending an Ajax request)
  2. Initiate other actions to update the Store
  3. Call other Sagas

There are many commands in Effects. For more information, see asynchronous API reference.

Redux-saga features

Easy to test, for example:

assert.deepEqual(iterator.next().value, call(Api.fetch, '/products'))
  1. Action can maintain its purity, and asynchronous operations are concentrated in saga for processing.
  2. Watch/worker (listener-> execute) work form
  3. Implemented as generator
  4. Good support for application scenarios that contain complex asynchronous Logic
  5. More fine-grained Implementation of asynchronous logic makes the process clearer and easier to track and solve bugs.
  6. Writing asynchronous logic in synchronous mode is more in line with human thinking Logic
  7. From redux-thunk to redux-saga

Assume that the user needs to verify that the user's username and password meet the requirements during logon.

Implementation Using redux-thunk

Logic for retrieving user data (user. js ):

// user.jsimport request from 'axios';// define constants// define initial state// export default reducerexport const loadUserData = (uid) => async (dispatch) => {  try {    dispatch({ type: USERDATA_REQUEST });    let { data } = await request.get(`/users/${uid}`);    dispatch({ type: USERDATA_SUCCESS, data });  } catch(error) {    dispatch({ type: USERDATA_ERROR, error });  }}

Verify the login logic (login. js ):

import request from 'axios';import { loadUserData } from './user';export const login = (user, pass) => async (dispatch) => {  try {    dispatch({ type: LOGIN_REQUEST });    let { data } = await request.post('/login', { user, pass });    await dispatch(loadUserData(data.uid));    dispatch({ type: LOGIN_SUCCESS, data });  } catch(error) {    dispatch({ type: LOGIN_ERROR, error });  }}

Redux-saga

Asynchronous logic can all be written into saga. js:

Export function * loginSaga () {while (true) {const {user, pass} = yield take (LOGIN_REQUEST) // wait for the specified action LOGIN_REQUEST try {let {data} = yield call (loginRequest, {user, pass}) on the Store; // blocking, requesting background data yield fork (loadUserData, data. uid); // non-blocking execution of loadUserData yield put ({type: LOGIN_SUCCESS, data}); // initiate an action, similar to dispatch} catch (error) {yield put ({type: LOGIN_ERROR, error}) ;}} export function * loadUserData (uid) {try {yield put ({type: USERDATA_REQUEST }); let {data} = yield call (userRequest, '/users/$ {uid}'); yield put ({type: USERDATA_SUCCESS, data});} catch (error) {yield put ({type: USERDATA_ERROR, error });}}

Difficulties

For redux-saga, there are still a lot of difficult-to-understand and obscure places. I will sort out the concepts that I think are easy to confuse:

Use of take

Both take and takeEvery listen to an action, but the roles of the two are different. takeEvery responds each time the action is triggered, while take responds only when the stream is executed to the take statement. TakeEvery only listens to the action and executes the corresponding processing function. It does not have much control over when to execute the action and how to respond to the action. The called task cannot control when it is called, in addition, they cannot control when to stop listening. It can only be called over and over again when each action is matched. However, the take function can determine when to respond to an action and subsequent operations after the response.
For example, to perform a logger operation when listening to all types of actions are triggered, use takeEvery to implement the following:

Import {takeEvery} from 'redux-saga 'function * watchAndLog (getState) {yield * takeEvery (' * ', function * logger (action) {// do some logger operation // In the callback function })}

The take implementation is as follows:

Import {take} from 'redux-saga/effects' function * watchAndLog (getState) {while (true) {const action = yield take ('*') // do some logger operation // parallel with take })}

Here, while (true) means that once the last step of the process (logger) is reached, a new iteration (logger process) is started by waiting for a new arbitrary action ).

Blocking and non-blocking

The call operation is used to initiate asynchronous operations. For generator, the call operation is a blocking operation, which cannot be executed or processed before the end of the Generator call ., However, fork is not a blocking operation. When fork transfers a task, the task is executed in the background. At this time, the execution stream can continue to be executed later without waiting for the result to be returned.

For example, the following logon scenario:

function* loginFlow() { while(true) {  const {user, password} = yield take('LOGIN_REQUEST')  const token = yield call(authorize, user, password)  if(token) {   yield call(Api.storeItem({token}))   yield take('LOGOUT')   yield call(Api.clearItem('token'))  } }}

If the result is not returned when the call requests authorize, but the user triggers the LOGOUT action, the LOGOUT will be ignored and not processed, because loginFlow is blocked in authorize, It is not executed to take ('logout ').

Execute multiple tasks at the same time

If you need to execute multiple tasks at the same time in a specific scenario, such as requesting users data and products data, use the following method:

Import {call} from 'redux-saga/effects' // synchronously execute const [users, products] = yield [call (fetch, '/users'), call (fetch, '/products')] // instead of // sequentially execute const users = yield call (fetch,'/users'), products = yield call (fetch, '/products ')

When yield is followed by an array, the operations in the array will be executed according to the execution rules of Promise. all, and genertor will block the execution of all effects.

Source code explanation

In every redux-saga project, the main file contains the following logic for adding sagas middleware to the Store:

const sagaMiddleware = createSagaMiddleware({sagaMonitor})const store = createStore( reducer, applyMiddleware(sagaMiddleware))sagaMiddleware.run(rootSaga)

CreateSagaMiddleware is the method exported from src/middleware. js, the core source code file of redux-saga:

export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) { ...  function sagaMiddleware({ getState, dispatch }) {  const channel = stdChannel()  channel.put = (options.emitter || identity)(channel.put)  sagaMiddleware.run = runSaga.bind(null, {   context,   channel,   dispatch,   getState,   sagaMonitor,   logger,   onError,   effectMiddlewares,  })  return next => action => {   if (sagaMonitor && sagaMonitor.actionDispatched) {    sagaMonitor.actionDispatched(action)   }   const result = next(action) // hit reducers   channel.put(action)   return result  } } ...  }

This logic mainly executes sagaMiddleware (). In this function, runSaga is assigned to sagaMiddleware. run and executed. Finally, middleware is returned. Next, let's look at the runSaga () logic:

export function runSaga(options, saga, ...args) {... const task = proc(  iterator,  channel,  wrapSagaDispatch(dispatch),  getState,  context,  { sagaMonitor, logger, onError, middleware },  effectId,  saga.name, ) if (sagaMonitor) {  sagaMonitor.effectResolved(effectId, task) } return task}

This function defines that a task object is returned, which is generated by proc. js:

export default function proc( iterator, stdChannel, dispatch = noop, getState = noop, parentContext = {}, options = {}, parentEffectId = 0, name = 'anonymous', cont,) { ... const task = newTask(parentEffectId, name, iterator, cont) const mainTask = { name, cancel: cancelMain, isRunning: true } const taskQueue = forkQueue(name, mainTask, end)  ...  next()  return task function next(arg, isErr){ ...   if (!result.done) {    digestEffect(result.value, parentEffectId, '', next)   }  ... }}

DigestEffect executes effecttrigadh () and runEffect (), that is, execute effect. runEffect () defines the corresponding functions for different effect executions, and each effect function is In proc. js implementation.

In addition to some core methods, redux-saga also provides a series of helper files, which are used to return an iterator-like object for later traversal and execution, this is not detailed here.

The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.