Custom Redux Middleware - Dispatch to beginning of middleware chain? - node.js

I am writing a custom middleware that needs to dispatch thunk actions. The problem is that the middleware is called after redux-thunk in the middleware chain, so I get the error Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. when using the provided dispatch.
export default function createMiddleware() {
return ({dispatch, getState}) => next => (action) => {
if(action.type !== 'FOO') {
return next(action);
}
dispatch(thunkActionHere); // this is the issue
}
}
I would like to dispatch this thunk action back to the beginning of the middleware chain so that redux-thunk can handle it. Is this possible?
update:
function createMiddleware(extraArgument) {
return function ({dispatch, getState}) {
return function (next) {
return function (action) {
switch (action.type) {
case 'FOO1':
dispatch({type: 'NORMAL_ACTION'}); // works fine
break;
case 'FOO2':
dispatch(function() {
return (dispatch, getState) => { // Error: Actions must be plain objects. Use custom middleware for async actions.
console.log('inside the thunk');
};
});
break;
default:
return next(action);
}
};
};
};
}
const middleware = createMiddleware();
middleware.withExtraArgument = createMiddleware;
export default middleware;
Here's my store configuration:
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, compose(
// Add other middleware on this line...
applyMiddleware(bugsnagErrorCatcherMiddleware()),
applyMiddleware(thunk.withExtraArgument({APIFactory, PusherManager})),
applyMiddleware(webrtcVideoMiddleware.withExtraArgument(PusherManager)), // this is the middleware above
applyMiddleware(bugsnagbreadcrumbLoggerMiddleware()),
)
);
return store;
}
I cannot put my middleware before redux-thunk because then it doesn't receive actions that thunks dispatch.

Dispatching inside the middleware chain will send the action to the start of the middleware chain, and will call the thunk as usual (Demo - look at the console).
Why?
The original store.dispatch() (before applying middlewares) checks if the action is a plain POJO, and if not throws an error:
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
When you applyMiddleware() the dispatch is replaced by a new method, which is the chain of middleware, that call the original store.dispatch() in the end. You can see it in the applyMiddleware method:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch // dispatch is now the original store's dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action) // this refers to the dispatch variable. However, it's not the original dispatch, but the one that was created by compose
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch) // dispatch is a composition of the chain, with the original dispatch in the end
return {
...store,
dispatch
}
}
}
btw - change your middleware to this, since the 1st function will prevent your middleware from working.
export default const createMiddleware = ({dispatch, getState}) => next => (action) => {
if(action.type !== 'FOO') {
return next(action);
}
dispatch(thunkActionHere); // this is the issue
}

It turns out the issue was in my store configuration. Using redux's compose caused the issue.
before:
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webrtcVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagbreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';
const PusherManager = new Pusher(false);
export default function configureStore(initialState) {
return createStore(rootReducer, initialState, compose(
applyMiddleware(bugsnagErrorCatcherMiddleware()),
applyMiddleware(thunk.withExtraArgument({APIFactory, PusherManager})),
applyMiddleware(webrtcVideoMiddleware(PusherManager)),
applyMiddleware(bugsnagbreadcrumbLoggerMiddleware())
));
}
after:
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webRTCVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagBreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';
const PusherManager = new Pusher(false);
export default function configureStore(initialState) {
const middleware = [
bugsnagErrorCatcherMiddleware(),
thunk.withExtraArgument({APIFactory, PusherManager}),
webRTCVideoMiddleware.withExtraArgument(PusherManager),
bugsnagBreadcrumbLoggerMiddleware(),
];
return createStore(rootReducer, initialState, applyMiddleware(...middleware));
}

We use composeWithDevTools from redux-devtools-extension. Same issue and same solution as noted above. Just needed to move to using applyMiddleware(...middlewares) instead of multiple applyMiddleware(middleware), applyMiddleware(middleware) as arguments to the composition.

Related

How to do chained node-redis transactions with singleton wrapper functions

I have a Typescript-backed Express.js project that uses a singleton Redis client.
The singleton includes wrapper functions to Redis commands needed for my application (e.g., SADD).
Singleton
Here is a snippet of my singleton Redis client service, which is relevant to my question:
/**
* Set up a singleton class instance to interface
* with the Redis database, along with helper async
* functions that provide functionality.
*/
var redis = require("redis");
// https://github.com/redis/node-redis/issues/1673
type RedisClientType = ReturnType<typeof redis.createClient>;
type RedisClientOptionsType = Parameters<typeof redis.createClient>[0];
export class Redis {
private static instance: Redis;
private static client: RedisClientType;
constructor() {
if (Redis.instance)
return Redis.instance;
Redis.instance = this;
Redis.client = null;
}
/* ... */
async initializeClient(options: RedisClientOptionsType) {
Redis.client = redis.createClient(options);
Redis.client.on('connect', function() {
Redis.instance.log('Client connected');
});
Redis.client.on('error', function(err: Error) {
Redis.instance.log(`Could not communicate with Redis client [${err}]`);
});
await Redis.client.connect();
}
async shutdownClient() {
await Redis.client.quit();
}
async multi() {
await Redis.client.multi();
}
async exec() {
await Redis.client.exec();
}
/* ... */
async sAdd(k: string, v: string) {
return await Redis.client.sAdd(k, v);
}
}
Individual calls to sAdd, sMembers, etc. work fine. So the client itself is initialized correctly, and it is able to process basic Redis calls.
What I would like to do is perform some chained transactions, e.g., from a rudimentary POST request using the singleton Redis client service, process some data from an uploaded file, and then add some key-value pairs (to start):
import { Redis } from '#/service/redis';
/* ... */
export const myPost = async (req: Request, res: Response) => {
const redis = new Redis(); // initialized client, as defined above
const k = 'my-key';
const v = 'my-value';
await redis
.multi()
.sAdd(k, v)
.exec();
}
Problem
The problem is that I get two errors with the chained await ... call.
First error
The first error is related to the await keyword, just before the multi/sAdd/exec call:
'await' expressions are only allowed within async functions and at the top levels of modules.ts(1308)
This await is within the async-ed post function. (I assume that I need to this handle the results of the underlying Promise chain.)
Second error
The second error is related to the sAdd wrapper:
Property 'sAdd' does not exist on type 'Promise<void>'.ts(2339)
I tried to add a return type to the call to multi:
async multi(): Promise<RedisClientType> {
await Redis.client.multi();
}
But this did not resolve the error with sAdd.
Question
What changes do I make to the service/singleton, which would allow calls to wrapper functions to be chained?

NestJS handle service exceptions

I'm working on a NestJS app where my services are not always called by a controller or any http request at all. Rather some services are called by a cron schedule to periodically fetch data.
What would be the best way to handle errors in this scenario? I implemented a "catch-all" exception filter, but when my service is called "internally" (not by a controller/request), there error does not get caught and I have an uncaught promise error.
See my question here: Use global nest module in decorator
This decorator catches errors of a class method and logs them. The logging part is not necessary, you could implement your own error handling logic.
import { Inject } from '#nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = false) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
With this decorator you can simply add the logErrorDecorator() to your service class methods

How to mock API client created inside a test class?

I have a component class as below which create the rest and websocket connections using a third party npm module. I could change the Component.constructor to accept the module as a dependency so that I can inject a mock version during my Jest testing. But I read about Mocks with Jest, I thought I want to try it, but I cannot seem to understand how to intercept Api.Rest() and Api.Websocket return values.
// component.ts
import * as Api from 'npm-module'
import * as wait from 'wait-for-stuff' // actual npm module
export class Component {
private _rest:any;
private _websocket:any;
public events = new EventEmitter();
constructor() {
// I want to intecept the return value of
// Api.Rest() and Api.Websocket() to use mock versions.
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
// so that when do stuff with this._rest and this._websocket;
// I can control what is the expected results during test
this._websocket.onUpdate((data) => {
events.emit('update', data);
});
var value = wait.for.promise(this._rest.getSomething());
}
}
Do I have to use another test library like Sinon or Jasmine?
Here is a simplified working example to get you started:
// #ts-ignore
import * as Api from 'npm-module'; // <= (ts-ignore since "npm-module" doesn't exist)
import EventEmitter from 'events';
jest.mock('npm-module', () => {
const getSomethingMock = jest.fn(); // <= always return...
const onUpdateMock = jest.fn(); // <= ...the same mocks...
return {
Rest: () => ({ getSomething: getSomethingMock }),
Websocket: () => ({ onUpdate: onUpdateMock })
}
},
{ virtual: true }); // <= (use virtual since "npm-module" doesn't exist)
class Component {
private _rest: any;
private _websocket: any;
public events = new EventEmitter();
constructor() {
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
this._websocket.onUpdate((data) => { // <= ...so that this onUpdate...
this.events.emit('update', data);
});
}
}
test('Component', () => {
const component = new Component();
const listener = jest.fn();
component.events.on('update', listener);
const onUpdate = new Api.Websocket().onUpdate; // <= ...is the same as this one
const onUpdateArrowFunction = onUpdate.mock.calls[0][0]; // <= get the arrow function passed to it
onUpdateArrowFunction('mock data'); // <= now call the function
expect(listener).toHaveBeenCalledWith('mock data'); // Success!
});
Details
Jest takes over the require system and allows you to specify what you want it to return when a module is required (note that TypeScript import statements get compiled to require calls).
One way to mock a module is to create a manual mock by creating a file at __mocks__/npm-module.ts that contains your mock.
Another way (show above) is to use jest.mock and pass it a module factory function.
Whenever the module is required during the test Jest will return the mocked module instead.
Note that the example above always returns the same mock for getSomething and onUpdate so those mock functions can be retrieved during the test.
Also note the use of mockFn.mock.calls to retrieve this arrow function:
(data) => {
this.events.emit('update', data);
}
...which gets passed to onUpdate. Once it has been retrieved, it can be called directly which triggers the listener as expected.

Jest asses called functions

I am testing my graphql endpoint with Jest. Basically I am assessing the result returned from resolver. But in this case, I want to test if a function has been called inside the resolver with particular parameter. The code is something like
import { sendLib } from '../lib'
export default {
send: async (input) => {
const data = {
foo: 1,
bar: 2,
...input
}
await sendLib(data)
return { input }
}
}
I want to make sure sendLib() is called with data parameter. The most important thing is I need to test if data has correct values. I have been reading the Jest's .toHaveBeenCalledWith() but I can't figured out how it fits my case.
Also is there a way to do that test without being actually executing sendLib()? Because the function calls a HTTP request and I don't really want to waste time to do that on testing.
you should mock lib in your test wit jest.fn that way it would not call your actual function implementation or perform http request. instead it would call the mock function. and you can assert what it was called with below is a rough example of what i'm tall
// filetotest.js
import { sendLib } from '../lib'
export default {
send: async (input) => {
const data = {
foo: 1,
bar: 2,
...input
}
await sendLib(data)
return { input }
}
}
//__tests__/filetotest.test.js
import { sendLib } from '../../lib'
import defaultImport from '../filetotest.js'
jest.mock('../../lib/', () => {
return {
sendLib: jest.fn()
}
})
test('call with correct data', async () => {
// call the function you are suppose to to test
await defaultImport()
// sendLib is a mockFn as jest.mock calls are hoisted
expect(SendLib).toHaveBeenCalledWith({data: ''})
})

Scope in NodeJS / Firebase promise

I'm trying to develop a NodeJS app connecting to Firebase. I can connect successfully, but I'm unable to figure how to manage the scope in the then call.
I'm using NodeJS 6.9.2
My test implementation looks like this:
const EventEmitter = require('events');
const fb = require('firebase')
class FireGateway extends EventEmitter {
constructor() {
super();
if ( this.instance ) {
return this.instance;
}
// INIT
var fbConfig = {
apiKey: "xxxxx",
authDomain: "xxxxx.firebaseapp.com",
databaseURL: "https://xxxxx.firebaseio.com/"
};
fb.initializeApp(fbConfig)
this.instance = this;
this.testvar = "aaa";
}
login() {
fb.auth().signInWithEmailAndPassword ("email", "pwd")
.catch(function(error) {
// Handle Errors here.
}).then( function(onresolve, onreject) {
if (onresolve) {
console.log(this.testvar);
// "Cannot read property 'testvar' of undefined"
this.emit('loggedin');
// error as well
}
})
}
}
module.exports = FireGateway;
------
...
var FireGateway = require('./app/fireGateway');
this.fireGW = new FireGateway();
this.fireGW.login();
....
Any idea how can I manage it?
The callback passed to then is being called asynchronously from another context, so the this doesn't correspond to the instantiated object.
Using ES6 arrow functions you can keep your object context, since an arrow function does not create its own this context.
By the way, the syntax you are using in the then method is not correct, then accepts two callbacks with one argument each one. Check the syntax here.
The catch before the then is not necessary as well I think, it would make more sense to put it at the end.
It would be something like this:
login() {
fb.auth().signInWithEmailAndPassword("email", "pwd")
.then(
(onResolve) => {
console.log(this.testvar);
this.emit('loggedin');
},
(onReject) = > {
// error handling goes here
});
}
On the other hand, it seems login method is doing an asynchronous operation, so you might want to wait for it to finish in your code. I would make the login method return a Promise, so you can wait for it outside:
login() {
return fb.auth().signInWithEmailAndPassword("email", "pwd")
...
}

Resources