Redux Saga ist eine Middleware-Bibliothek für Redux, die die Verwaltung von Nebenläufigkeiten (Side Effects) in Redux-Anwendungen vereinfacht. Durch die maximale Nutzung der Generator-Funktionen (function*
) von ES6 ermöglicht sie das Schreiben von asynchronem Code, der wie synchroner Code aussieht.
Redux Saga existiert nicht nur in der JavaScript-Welt, sondern wird auch als Design Pattern betrachtet. Das Saga Pattern ist eine Möglichkeit, lange Transaktionen mit vielen Side Effects oder potenziellen Risiken zu verwalten. Für jede erfolgreiche Transaktion muss es eine Gegentransaktion geben, um die Transaktion im Fehlerfall in den ursprünglichen Zustand zurückzusetzen.
Diagramm zur Veranschaulichung des Saga-Patterns in der Transaktionsverwaltung
Side Effects sind Aufgaben in der funktionalen Programmierung, die mit der Außenwelt interagieren müssen, z. B. das Abrufen von Daten über eine API, das Speichern von Daten im LocalStorage oder das Lesen von Cookies aus dem Browser. Diese Aufgaben sind in der Regel asynchron und können den Zustand der Anwendung verändern. Reducer in Redux müssen synchron und rein sein, d. h. sie dürfen nur Logik basierend auf der Eingabe verarbeiten und eine Ausgabe zurückgeben, ohne Side Effects zu verursachen. Daher wird Redux Saga verwendet, um Side Effects außerhalb des Reducers zu behandeln und den Reducer einfach und testbar zu halten.
Eine Generator-Funktion ist eine spezielle Art von Funktion in JavaScript, die die Ausführung pausieren und mehrmals einen Wert zurückgeben kann. Das Schlüsselwort yield
wird verwendet, um die Ausführung zu pausieren und einen Wert zurückzugeben. Im Gegensatz zu einer normalen Funktion, die einmal ausgeführt wird und ein Ergebnis zurückgibt, kann eine Generator-Funktion ausgeführt werden, pausieren, ein Ergebnis zurückgeben und die Ausführung von der unterbrochenen Stelle fortsetzen.
Redux Saga funktioniert, indem es auf die dispatcheten Aktionen lauscht. Wenn eine Aktion dispatched wird, prüft Redux Saga, ob es sich um eine relevante Aktion handelt. Wenn ja, führt es eine entsprechende Generator-Funktion aus. Diese Generator-Funktion yield
et Effekt-Objekte. Effekt-Objekte sind Objekte, die Anweisungen für die Middleware von Redux enthalten, um andere Aktionen auszuführen, z. B. eine asynchrone Funktion aufzurufen oder eine neue Aktion an den Store zu senden (put
).
Warum also Redux Saga verwenden? Im Vergleich zu Redux Thunk, einer anderen Middleware, die häufig zur Behandlung von Side Effects verwendet wird, bietet Redux Saga viele Vorteile. Redux Thunk führt bei der Verarbeitung mehrerer verschachtelter asynchroner Aufgaben oft zu „Callback Hell“, was den Code schwer lesbar und wartbar macht. Redux Saga hingegen macht den Code durch die Verwendung von Generator-Funktionen und Effekt-Objekten lesbarer, verständlicher und testbarer. Redux Saga ermöglicht das einfache Testen von asynchronen Abläufen und hält Aktionen rein.
Beispiel für einen Vergleich zwischen Redux Thunk und Redux Saga bei der Verarbeitung einer API-Anfrage:
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 trennt die Logik zur Behandlung von Side Effects von Komponenten und Reducern, wodurch der Code leichter zu warten, zu erweitern und zu testen ist. Die Verwendung von try/catch
in Redux Saga vereinfacht die Fehlerbehandlung im Vergleich zur Verwendung von Promise Chains in Redux Thunk.