How to do it...

Build a simple RESTful API server that will have two endpoints or answer to paths /time and /date when a GET request is made. However, on /date path, we will pretend that there is an internal error and make the request fail in order to see how to handle errors in asynchronous requests as well:

  1. Create a new file named api-server.js
  2. Include the ExpressJS library and initialize a new ExpressJS application:
      const express = require('express') 
      const app = express() 
  1. For /time path, simulates a delay of 2s before sending a response:
      app.get('/time', (req, res) => { 
          setTimeout(() => { 
              res.send(new Date().toTimeString()) 
          }, 2000) 
      }) 
  1. For /date path, simulates a delay of 2s before sending a failed response:
      app.get('/date', (req, res) => { 
          setTimeout(() => { 
              res.destroy(new Error('Internal Server Error')) 
          }, 2000) 
      }) 
  1. Listen on port 1337 for new connections
      app.listen( 
          1337, 
          () => console.log('API server running on port 1337'), 
      ) 
  1. Save the file

As for the client, build a NodeJS application using Redux that will dispatch synchronous and asynchronous actions. Write a middleware function to allow the dispatch method to handle asynchronous actions:

  1. Create a new file named async-redux.js
  2. Include the node-fetch and Redux libraries:
      const fetch = require('node-fetch') 
      const { 
          createStore, 
          applyMiddleware, 
          combineReducers, 
          bindActionCreators, 
      } = require('redux') 
  1. Define three kinds of status. Each status represents the state of an asynchronous operation:
      const STATUS = { 
          PENDING: 'PENDING', 
          RESOLVED: 'RESOLVED', 
          REJECTED: 'REJECTED', 
      } 
  1. Define two action types:
      const TYPE = { 
          FETCH_TIME: 'FETCH_TIME', 
          FETCH_DATE: 'FETCH_DATE', 
      } 
  1. Define action creators. Notice that the value property is an asynchronous function in the first two action creators. Your, later defined, middleware function will be responsible for making Redux understand these actions:
      const actions = { 
          fetchTime: () => ({ 
              type: TYPE.FETCH_TIME, 
              value: async () => { 
                  const time = await fetch( 
                      'http://localhost:1337/time' 
                  ).then((res) => res.text()) 
                  return time 
              } 
          }), 
          fetchDate: () => ({ 
              type: TYPE.FETCH_DATE, 
              value: async () => { 
                  const date = await fetch( 
                      'http://localhost:1337/date' 
                  ).then((res) => res.text()) 
                  return date 
              } 
          }), 
          setTime: (time) => ({ 
              type: TYPE.FETCH_TIME, 
              value: time, 
          }) 
      } 
  1. Define a common function for setting values from an action object that will be used in your reducer:
      const setValue = (prevState, action) => ({ 
          ...prevState, 
          value: action.value || null, 
          error: action.error || null, 
          status: action.status || STATUS.RESOLVED, 
      }) 
  1. Define the initial state of your application:
      const iniState = { 
          time: { 
              value: null, 
              error: null, 
              status: STATUS.RESOLVED, 
          }, 
          date: { 
              value: null, 
              error: null, 
              status: STATUS.RESOLVED, 
          } 
      } 
  1. Define a reducer function. Notice that it is only one reducer that handles two slices of the state, the time and the date:
      const timeReducer = (state = iniState, action) => { 
          switch (action.type) { 
              case TYPE.FETCH_TIME: return { 
                  ...state, 
                  time: setValue(state.time, action) 
              } 
              case TYPE.FETCH_DATE: return { 
                  ...state, 
                  date: setValue(state.date, action) 
              } 
              default: return state 
          } 
      } 
  1. Define a middleware function that will check whether a dispatched action type has a function as the value property. If that is so, assume that the value property is an async function. First, we dispatch an action to set the status as PENDING. Then, when the async function is resolved, we dispatch another action to set the status as RESOLVED or in case of an error as REJECTED:
      const allowAsync = ({ dispatch }) => next => action => { 
          if (typeof action.value === 'function') { 
              dispatch({ 
                  type: action.type, 
                  status: STATUS.PENDING, 
              }) 
              const promise = Promise 
                  .resolve(action.value()) 
                  .then((value) => dispatch({ 
                      type: action.type, 
                      status: STATUS.RESOLVED, 
                      value, 
                  })) 
                        .catch((error) => dispatch({ 
                      type: action.type, 
                      status: STATUS.REJECTED, 
                      error: error.message, 
                  })) 
              return promise 
          } 
          return next(action) 
      } 
  1. Create a new store and apply your defined middleware function to extend the functionality of the dispatch method:
      const store = createStore( 
          timeReducer, 
          applyMiddleware( 
              allowAsync, 
          ), 
      ) 
  1. Bind action creators to the dispatch method of the store:
      const { 
          setTime, 
          fetchTime, 
          fetchDate, 
      } = bindActionCreators(actions, store.dispatch) 
  1. Subscribe a function listener to the store and display in terminal the state tree, as a JSON string, every time there is a change in the state:
      store.subscribe(() => { 
          console.log('x1b[1;34m%sx1b[0m', 'State has changed') 
          console.dir( 
              store.getState(), 
              { colors: true, compact: false }, 
          ) 
      }) 
  1. Dispatch a synchronous action to set the time:
      setTime(new Date().toTimeString()) 
  1. Dispatch an asynchronous action to fetch and set the time:
      fetchTime() 
  1. Dispatch another asynchronous action to fetch and try to set the date. Remember that this operation is supposed to fail and it's intentional:
      fetchDate() 
  1. Save the file.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.89.173