Redux Saga is een middleware-bibliotheek voor Redux die het beheren van side-effects in een Redux-applicatie vereenvoudigt. Door maximaal gebruik te maken van de Generators (function*
) functionaliteit van ES6, maakt het asynchrone code mogelijk die eruitziet als synchrone code.
Redux Saga bestaat niet alleen in de JavaScript-wereld, maar wordt ook beschouwd als een design pattern. Het Saga pattern is een manier om lange transacties met meerdere side-effects of potentiële risico’s te beheren. Voor elke succesvolle transactie moet er een counter-transactie zijn om de transactie terug te draaien naar de oorspronkelijke staat als er een probleem optreedt.
Diagram van het Saga Pattern in transactiebeheer
Side-effects zijn taken in functioneel programmeren die interactie vereisen met de buitenwereld, zoals het aanroepen van een API om gegevens op te halen, gegevens op te slaan in localStorage of cookies uit de browser te lezen. Deze taken zijn vaak asynchroon en kunnen de status van de applicatie wijzigen. Reducers in Redux moeten synchroon en puur zijn, wat betekent dat ze alleen logica verwerken op basis van input en output retourneren zonder side-effects te veroorzaken. Daarom wordt Redux Saga gebruikt om side-effects buiten de Reducer af te handelen, waardoor de Reducer eenvoudig en testbaar blijft.
Een Generator function is een speciaal type functie in JavaScript dat de uitvoering kan pauzeren en meerdere keren een waarde kan retourneren. Het sleutelwoord yield
wordt gebruikt om de uitvoering te pauzeren en een waarde te retourneren. In tegenstelling tot een normale functie die eenmaal uitvoert en een resultaat retourneert, kan een Generator function uitvoeren, pauzeren, een resultaat retourneren en de uitvoering hervatten vanaf het punt waar het was gepauzeerd.
Redux Saga werkt door te luisteren naar acties die worden verzonden (dispatched). Wanneer een actie wordt verzonden, controleert Redux Saga of die actie relevant is. Zo ja, dan voert het een corresponderende generator function uit. Deze generator function yield
effect objects. Effect objects zijn objecten die instructies bevatten voor de middleware van Redux om andere acties uit te voeren, zoals het aanroepen van een asynchrone functie of het verzenden van een nieuwe actie naar de store.
Waarom zou je Redux Saga gebruiken? Vergeleken met Redux Thunk, een andere veelgebruikte middleware voor het afhandelen van side-effects, biedt Redux Saga verschillende voordelen. Redux Thunk leidt vaak tot “callback hell” bij het verwerken van meerdere geneste asynchrone taken, waardoor de code moeilijk te lezen en te onderhouden is. Redux Saga, met behulp van generator functions en effect objects, maakt de code leesbaarder, begrijpelijker en testbaarder. Redux Saga maakt het eenvoudig om asynchrone flows te testen en zorgt ervoor dat acties puur blijven.
Voorbeeld van een vergelijking tussen Redux Thunk en Redux Saga bij het verwerken van een API-request:
Redux Thunk:
import { API_BUTTON_CLICK, API_BUTTON_CLICK_SUCCESS, API_BUTTON_CLICK_ERROR } from './actions/consts';
import { getDataFromAPI } from './api';
const getDataStarted = () => ({ type: API_BUTTON_CLICK });
const getDataSuccess = data => ({ type: API_BUTTON_CLICK_SUCCESS, payload: data });
const getDataError = message => ({ type: API_BUTTON_CLICK_ERROR, payload: message });
const getDataFromAPI = () => {
return dispatch => {
dispatch(getDataStarted());
getDataFromAPI()
.then(data => {
dispatch(getUserSuccess(data));
})
.catch(err => {
dispatch(getDataError(err.message));
});
};
};
Redux Saga:
import { call, put, takeEvery } from 'redux-saga/effects';
import { API_BUTTON_CLICK, API_BUTTON_CLICK_SUCCESS, API_BUTTON_CLICK_ERROR } from './actions/consts';
import { getDataFromAPI } from './api';
export function* apiSideEffect(action) {
try {
const data = yield call(getDataFromAPI);
yield put({ type: API_BUTTON_CLICK_SUCCESS, payload: data });
} catch (e) {
yield put({ type: API_BUTTON_CLICK_ERROR, payload: e.message });
}
}
export function* apiSaga() {
yield takeEvery(API_BUTTON_CLICK, apiSideEffect);
}
Redux Saga scheidt de logica voor het afhandelen van side-effects van componenten en reducers, waardoor de code gemakkelijker te onderhouden, uit te breiden en te testen is. Het gebruik van try/catch
in Redux Saga maakt foutverwerking eenvoudiger dan het gebruik van promise chains in Redux Thunk.