Sagas vs Thunk

En contraste al flujo de datos aparentemente completo que ofrece Redux para nuestra aplicación, hay una cosa que no hace por nosotros: recoger dichos datos de un servicio web ya que Redux no se ocupa de los “efectos secundarios”.

De la forma en la que React y Redux plantean una programación funcional, el resultado de una función debe ser siempre predecible en función a los parámetros de inicio.

Dicho de otra forma: un reductor siempre devolverá el mismo resultado si se le pasa un estado y una acción determinados y un componente React siempre se mostrará igual si se le pasan unas propiedades conocidas.

Esto está muy bien ya que nuestra aplicación tendrá siempre un comportamiento predecible y seguro. Pero falta algo importante: las llamadas a servicios web, lectura y escritura de cookies, uso de las API’s del navegador como geolocalizador, base de datos, operaciones en el tiempo, etc…

Ninguna aplicación puede existir sin estos “efectos secundarios”, particularmente las llamadas a servicios, por lo cual surgen librerías middleware para Redux que nos permiten hacernos cargo de ellos.

Mientras que el más extendido desde el inicio es Thunk, Sagas es considerado un favorito por la comunidad y este post intentaré explicar por qué.

Así queda un generador de acción que incluye un thunk:

import { loadingActionGenerator, successActionGenerator, failureActionGenerator } from ‘../actions’;

const actionGenerator = ({ dispatch }) => {
  dispatch(loadingActionGenerator());
  fetch('https://api-url.com/give-me-data')
    .then(result => dispatch(successActionGenerator(result))
    .catch(() => dispatch(failureActionGenerator())
}

export default actionGenerator;

La ventaja de una generadora de acción con Thunk es que nos permite añadirle lógica y bifurcar el resultado dependiendo de las condiciones, ¿verdad? Como veremos más adelante, ese es precisamente el problema con este patrón.

Así sería la implementación tipo de una saga:

import { call, put, spawn, takeLatest } from 'redux-saga/effects';
import * as types from 'actions/actionTypes';
import { actionSucceeded, actionFailed } from 'actions';
import { doSomethingAPI } from 'APICalls';

function* somethingRequested(action) {
	const params = action.payload;
	try {
		const result = yield call(doSomethingAPI, params);
		yield put(actionSucceeded({ result }));
	} catch (error) {
		yield put(actionFailed({ message: error.message }));
	}
}

function* somethingRequestedSaga() {
	yield takeLatest(types.SOMETHING_REQUESTED, somethingRequested);
}

function* mySaga() {
	yield spawn(somethingRequestedSaga);
}

export default mySaga;

Para empezar, parece una implementación más larga, pero también hace un uso extensivo de una librería tal vez desconocida para nosotros ¿y desde cuándo lleva un asterisco la palabra reservada function*?

Las funciones con asterisco son funciones generadoras y forman parte del standard de Javascript desde ES6 y de otros lenguajes desde mucho antes. Para resumir, son funciones cuya ejecución no es automática sino que queda aplazada y se reproduce a pasos.

A pesar de ser standard, es común no conocer las funciones generadoras si no se usan sagas y esto puede ser un obstáculo a la hora de introducir sagas en un equipo de desarrollo.

Por encima de sus desventajas, Sagas sigue siendo considerada la implementación de efectos secundarios superior para Redux y la razón principal debe ser la forma en la que se integra en el flujo de datos de la aplicación.

Fig. Tu aplicación con Thunk.

La simplicidad de Thunk es también una infracción del patrón Redux, que establece que las acciones deben ser mensajes sencillos y no tener lógica, cuando Thunk nos invita precisamente a incluir lógica en los generadores de acción. Funciona, hace un buen servicio y es fácil de implementar pero es también una violación del patrón Redux.

Fig. Tu aplicación con Sagas

Por la parte contraria, sin embargo, la aparente complejidad de sagas se convierte en un esquema más sencillo de ver y respetuoso con el patrón Redux original, extendiéndolo con un nuevo módulo a la misma altura de los reductores, pero con características propias.

Redux es una implementación pub-sub en el que las acciones son emitidas en lugar de dirigidas a un objeto concreto. Originalmente, las acciones se emiten y es el reductor y sus partes los que deciden si van a reaccionar a la instrucción y cómo.

Con Sagas, disponemos de un nuevo módulo que también reacciona a las acciones emitidas a través de la aplicación como hacen ya los reductores, pero este módulo sí se permite tener lógica, aplazar la ejecución de las funciones y lanzar nuevas acciones cuando termine con la ventaja de que el resto de módulos de Redux siguen haciendo su trabajo sin ningún tipo de corrupción contra la arquitectura original.

En un flujo de ejecución óptima de llamada a un servicio web, la saga reacciona al requerimiento de esta llamada, la ejecuta y emite las acción de éxito correspondiente, que recogerá un reductor.

Los reductores, por su parte y en paralelo, han podido reaccionar al inicio de esta llamada (para ponerse en estado loading, por ejemplo) y a los nuevos datos recibidos a través de la accioń de éxito.

A la ventaja de su patrón, se le suma el de la versatilidad ya que las sagas serán un módulo vivo en la aplicación que se quedará escuchando cualquier acción que se inicie por parte de sockets, servicios basados en eventos como Firebase, etc… la versatilidad de Sagas no ha sido completamente descubierta aún y son ya el patrón recomendado para efectos secundarios en Redux.

Autodidacta del código desde pequeñito en un viaje a través de un montón de lenguajes y tecnologías. En la actualidad especializado en React, anteriormente en Ruby on Rails y en el futuro... ¡quién sabe!

Ver toda la actividad de Abel Tamayo

Escribe un comentario