npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-yield-effect

v1.1.0

Published

Declarative side-effects for redux with generators

Downloads

12

Readme

CircleCI npm version

redux-yield-effect

Declarative side effects for redux with generators

redux-yield-effect middleware allows to write action creators as easily testable side-effect free generators.

It provides extensible set of operators which allow to describe any possible side effect (API service call, action dispatch, etc.) as a plain javascript object that is handled and executed on the level of middleware, so that user code remains side-effect free.

Motivation

This library is strongly inspired by the awesome redux-saga project. Actually the API of the redux-yield-effect almost completely copies one from the redux-saga. But even though these libs have a lot of similarities, they are different in a very important aspect - the way of kicking off the effect generators. redux-saga promotes the approach of long-running daemon processes that are listening to an action/event to start/resume execution, whereas in redux-yield-effect you kick off the effect generator by simply dispatching it (approach similar to the redux-thunk). You may read more about the motivation behind it here.

Installation

npm install --save redux-yield-effect

Usage

Check complete example here

import { createStore, applyMiddleware } from 'redux';
import { createYieldEffectMiddleware } from 'redux-yield-effect';
import { put, call, fork, join } from 'redux-yield-effect/lib/effects';


const store = createStore(
    rootReducer,
    applyMiddleware(createYieldEffectMiddleware()) // apply redux-yield-effect middleware
);

// dispatch business logic coroutine
store.dispatch(orderProduct('PRDCT_ID_1122', 'USR_ID_9999'))
    .then(
        (order) => console.log('orderProduct result:', order),
        (error) => console.error('order failed with error', error)
    );

// main business logic coroutine
function* orderProduct(productId, userId) {
  // load user address and product price in the background
  // "fork" calls a function or a coroutine and continues execution without waiting for a result
  // we will "join" that result later
  const fetchUserAddressFromDBTask = yield fork(fetchUserAddressFromDB, userId);
  const fetchProductPriceFromDBTask = yield fork(fetchProductPriceFromDB, userId);

  try {
    // reserve the product
    // "call" calls a function or a coroutine and waits until it asynchronously resolves
    yield call(reserveProduct, productId);

    // fetch user payment information
    const userPaymentDetails = yield call(fetchUserPaymentDetails, userId);
    // "put" dispatches action
    yield put({ type: 'UPDATE_USER_CARD_NUMBER', payload: userPaymentDetails.cardNumber });

    // make the payment
    // here we "join" the result of the previously called function "fetchProductPriceFromDB", so wait until it is done
    const { price } = yield join(fetchProductPriceFromDBTask);
    // here we "call" a coroutine (another generator that yields declarative effects)
    yield call(makePayment, userPaymentDetails.cardNumber, price);

    // add shipping address and complete order
    const { address } = yield join(fetchUserAddressFromDBTask);
    yield put({ type: 'UPDATE_USER_ADDRESS', payload: address });
    const order = yield call(completeOrder, productId, userId, address);
    yield put({ type: 'COMPLETE_ORDER', payload: order.orderId });

    return order;
  } catch (error) {
    // if any of the yielded effects from the "try" block fails, we could catch that error here

    // cancel product reservation and report error
    yield call(cancelProductReservation, productId);
    yield put({ type: 'ORDER_FAILED', error });

    // re-throw error to the caller
    throw error;
  }
}

// payment coroutine
function* makePayment(cardNumber, amount) {
  const validationResult = yield call(validateCard, cardNumber);

  if (validationResult.status !== 'success') {
    throw new Error(`card number ${cardNumber} is not valid`);
  }

  yield put({ type: 'CARD_VALIDATION_SUCCESS' });
  yield call(pay, cardNumber, amount);
  yield put({ type: 'PAYMENT_COMPLETE' });
}

How it works

Each effect creator (put, call, etc.) instead of performing real side effect returns just a plain object that describes the effect. For example call(myApiService, 123, 'foo') will produce:

{ 
  type: 'YIELD_EFFECT_CALL',
  payload: {
    func: myApiService,
    args: [123, 'foo']
  }
}

When yielded from an action creator this effect description is picked up by the middleware and handed over to a corresponding effect processor based on the type property. Effect processor performs that side effect and the eventual result/error of it is returned/thrown back to the action creator at the place the effect was yielded from.

This approach allows developer to write pure action creators that may define complex business logic with async execution flow, yet being trivial to test.

API

createYieldEffectMiddleware(customEffectProcessors?): Function

Creates redux middleware that handles effect coroutines.

  • customEffectProcessors: { [effectType: string]: [effectProcessor: Function] } - optional - object that specifies the mapping between custom effect's type and effectProcessor function. This allows to create and use your own or third party effect creators with redux-yield-effect

Effect creators

call(func, ...args): Effect

Creates an Effect that when performed should call func with args as arguments. When Effect yielded, if func is a normal function, coroutine is suspended until the Promise returned by func fulfilled. If func is an effect coroutine then execution waits until coroutine returns.

  • func: Function = () => {Promise<any> | any} | GeneratorFunction - function or effect coroutine
  • args: Array - arguments to call func with

fork(func, ...args): Effect

Creates an Effect that when performed should call func with args as arguments. Unlike call doesn't suspend the execution flow, but instantly returns a Task object, that could further be used with join effect creator to get the result of function call.

  • func: Function = () => {Promise} | GeneratorFunction - function or effect coroutine
  • args: Array - arguments to call func with

join(task): Effect

Creates an Effect that when performed suspends the execution flow until previously forked task is finished.

  • task - object returned from a previous fork call

put(action): Effect

Creates an Effect that when performed dispatches the action with redux's store.dispatch method.

  • action: Action - action to dispatch

Custom effect creators

It is possible to create your own custom effect creators. Let's learn how to make it by example

// ======================== log.js ==========================
// In order to create a custom effect creator you need to define three things:

// 1. string constant, that represents the type of the Effect
export const TYPE = '__YIELD_EFFECT_LOG__';

// 2. effect creator - function that returns Effect description object
export default function log(message) {
    return {
        type: TYPE,
        payload: {
            message: message
        }
        
    };
}

// 3. effect processor - function that knows how to process certain Effect.
// It should always return Promise
export function processor(effect, { dispatch, effectGeneratorProcessor }) {
    const message = effect.payload.message;
    
    return Promise.resolve().then(() => { console.log(message); });
}

// ======================= main.js ==========================
import log, { TYPE as LOG_EFFECT_TYPE, processor as logEffectProcessor } from './log';
import { createStore, applyMiddleware } from 'redux';
import { createYieldEffectMiddleware } from 'redux-yield-effect';

// Now we should let middleware know how to handle our Effect:
const yieldEffectMiddleware = createYieldEffectMiddleware({
    [LOG_EFFECT_TYPE]: logEffectProcessor
});

const store = createStore(
    reducer,
    applyMiddleware(yieldEffectMiddleware) // apply redux-yield-effect middleware
);

store.dispatch(function* () {
    // now you can use your custom effect creator in your effect coroutine
    yield log('this will be logged in the "logEffectProcessor".');
});