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

reservice

v0.1.9

Published

An isomorphic async tasks solution for redux

Downloads

14

Readme

reservice

An isomorphic/universal asynchronous tasks solution for redux.

npm version Build Status Test Coverage License

You may already using redux-thunk for your asynchronous tasks. Put thunk, function or promise into an action makes it not pure, which means the action may not be serialized or replayed well.

A better asynchronous task practice is: create your action as pure object, do asynchronous tasks in your own redux middlewares. This practice keep all your actions creators and reducers pure and clean, make your application more isomorphic or universal. The only place you put asynchronous codes are redux middlewares....Or, a better place: redux service (a.k.a. re-service).

A redux service means: an asynchronous task triggered by a start service action. After it done, the result will be dispatched as another service done action.

Re-service provides a good practice for all your asynchronous tasks, includes:

  • A helper function to create service action creator. (the action is FSA compliant)
  • A redux middleware to:
    • handle the service action
      • At client side, transport action to server then get result.
      • At server side, execute the service then get result.
    • then dispatch service result action
  • An express middleware to deal with transported service actions.

Install

npm install reservice --save

You will need these polyfills for older browsers or other environments:

Usage

A Service

// req is optional, you can receive req to deal with session based tasks.
const myService = (payload, req) => {
  // Do any async task you like, return a promise or result.
  // You can not know any redux related things,
  // but you can access the express request object here.
  ...
  return result;
}

A Service Action Creator

import { createService } from 'reservice';

// Check redux-actions to know more about payloadCreator
// https://github.com/acdlite/redux-actions#createactiontype-payloadcreator--identity-metacreator
const doSomeThing = createService('DO_SOMETHING', payloadCreator);

expect(doSomeThing('good')).toEqual({
  type: 'CALL_RESERVICE',
  payload: 'good',
  reservice: {
    name: 'DO_SOMETHING',
    state: 'CREATED',
  },
});

Define Service List

const serviceList = {
  [doSomeThing]: myService,
  [anotherServiceCreator]: theCodeOfAnotherService,
  ...
}

Setup Express Application

// your server.js
import { createMiddlewareByServiceList } from 'reservice';
import bodyParser from 'body-parser';

...

// reservice middleware need to access body as json
app.use(bodyParser.json());

// Add this line to declare serviceList and use the express middleware
app.use(createMiddlewareByServiceList(serviceList));

The Reducer

import { handleActions } from 'redux-actions';

// create a reducer
const myReducer = handleActions({
  [doSomeThing]: (state, action) => { ... },
  [anotherServiceCreator]: anotherReducer,
  ...
}, initialState);

// If you also want to take care service start, try this
import { ACTION_TYPE_RESERVICE } from 'reservice';

const nowLoadingReducer = (state = initialState, action) => {
  if (action.type !== ACTION_TYPE_RESERVICE) {
    return state;
  }

  // service started, remeber to set nowLoading to false in yourown reducers
  // you can set different loading states by checking action.reservice.name
  return { ...state, nowLoading: true };
}

Setup Redux Store

import { createStore, applyMiddleware } from 'redux';
import { serviceMiddleware, settleRequest } from 'reservice';

const store = createStore(
  myReducer,
  applyMiddleware(serviceMiddleware)
);

// Optional: If you like to access request in service
// You need to do this.
store.dispatch(settleRequest(req));

Example

Please check example to get a deeper guide.

Debug

  • reservice already adopt debug , you can export DEBUG=... to show debug log:
    • reservice:start : show service name, payload
    • reservice:receive : show service name, payload when client side dispatch received
    • reservice:success : show service name, payload and result when service successed
    • reservice:fail : show service name, payload and error when service failed
    • reservice:error : show service name, payload and error.stack when service failed
    • reservice:select : show service name, payload and selected result when service successed, refer to selector.

Optional Usage: createService

If you plan to migrate from redux-thunk and prefer to reuse your action type, you may specify the startType and endType when you createService.

const doSomeThing = createService({
  endType: 'DO_SOMETHING',
  startType: 'DO_SOMETHING_STARTED',
  payloadCreator,
  metaCreator,
});

store.dispatch(doSomeThing('good'));

expect(store.dispatch.calls.argsFor(0)).toEqual([{
  type: 'CALL_RESERVICE',
  payload: 'good',
  reservice: {
    name: 'DO_SOMETHING',
    start: 'DO_SOMETHING_STARTED',
    state: 'CREATED',
  },
}]);

// After the reservice middleware handled the action,
// another action will be dispatched.
expect(store.dispatch.calls.argsFor(1)).toEqual([{
  type: 'DO_SOMETHING_STARTED',
  payload: 'good',
  reservice: {
    name: 'DO_SOMETHING',
    start: 'DO_SOMETHING_STARTED',
    state: 'CREATED',
    ...
  },
}]);

// After the service is done, this action will be dispatched.
expect(store.dispatch.calls.argsFor(2)).toEqual([{
  type: 'DO_SOMETHING',
  payload: 'the result...',
  error: false,
  reservice: {
    name: 'DO_SOMETHING',
    start: 'DO_SOMETHING_STARTED',
    state: 'END',
    ...
  },
}]);

Optional Usage: Selector

In most case you may try to refine API response data by selector function then put it into redux store. You can do it inthe service, or do it in the reducer.

If you run selector in the service, you dispatch only selected data to reducer. This practice save time and space when the smaller service result be transmitted from server to client, but prevent you to see full API response in network or redux debugging tools.

If you run selector in the reducer, you dispatch full data to reducer. This practice may take more time and space to transmit result from server to client, but you can see full API response in debug tools.

Reservice provide two small functions to help you adopt all these two practices in development and production environments:

// The selector function
const mySelector = result => ({
  data: result.body.data.reduce(refineMyData, {}),
  error: result.body.error
});

// In a service
import { prodSelect } from 'reservice';
// Run your selector only when in production environment, keep original result when in development environment.
const myProdSelector = prodSelect(mySelector);
const myService = payload => callAPI(payload).then(result => myRrodSelector(result));

// In a reducer
import { devSelect } from 'reservice';
// Run your selector only when in development environment, keep original result when in production environment.
const myDevSelector = devSelect(mySelector);
const myReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'MY_SERVICE':
      return { ...state, ...myDevSelector(action.payload) }
  }
  return state;
}

Advanced Usage: Selector

Or, you can define service as { service, selector } , reservice will keep full result in action.reservice.full_payload for debugging when not in production environment. And, the selected result still be placed in action.payload.

// Original Service code with result selector
const selectResult = (result) => result.body.items;
export myService = (payload, req) => callSomeApi({ ...payload, req }).then(selectResult);

// change export from function into { service , selector } for better debugging info
export myService = {
  service: (payload, req) => callSomeApi({ ...payload, req }),
  selector: (result) => result.body.items,
};

Here is a migration example to adopt reservice selector.