redux-promise-middleware-global-action
Version:
Redux middleware for promises, async functions and conditional optimistic updates
211 lines (172 loc) • 7.98 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.REJECTED = exports.FULFILLED = exports.PENDING = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports.default = promiseMiddleware;
var _lodash = require('lodash');
var _isPromise = require('./isPromise.js');
var _isPromise2 = _interopRequireDefault(_isPromise);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// The default async action types
var PENDING = exports.PENDING = 'PENDING';
var FULFILLED = exports.FULFILLED = 'FULFILLED';
var REJECTED = exports.REJECTED = 'REJECTED';
var defaultTypes = [PENDING, FULFILLED, REJECTED];
/**
* Function: promiseMiddleware
* Description: The main promiseMiddleware accepts a configuration
* object and returns the middleware.
*/
function promiseMiddleware() {
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var PROMISE_TYPE_SUFFIXES = config.promiseTypeSuffixes || defaultTypes;
var PROMISE_TYPE_DELIMITER = config.promiseTypeDelimiter || '_';
return function (ref) {
var dispatch = ref.dispatch;
return function (next) {
return function (action) {
/**
* Instantiate variables to hold:
* (1) the promise
* (2) the data for optimistic updates
*/
var promise = void 0;
var data = void 0;
/**
* There are multiple ways to dispatch a promise. The first step is to
* determine if the promise is defined:
* (a) explicitly (action.payload.promise is the promise)
* (b) implicitly (action.payload is the promise)
* (c) as an async function (returns a promise when called)
*
* If the promise is not defined in one of these three ways, we don't do
* anything and move on to the next middleware in the middleware chain.
*/
// Step 1a: Is there a payload?
if (action.payload) {
var PAYLOAD = action.payload;
// Step 1.1: Is the promise implicitly defined?
if ((0, _isPromise2.default)(PAYLOAD)) {
promise = PAYLOAD;
}
// Step 1.2: Is the promise explicitly defined?
else if ((0, _isPromise2.default)(PAYLOAD.promise)) {
promise = PAYLOAD.promise;
data = PAYLOAD.data;
}
// Step 1.3: Is the promise returned by an async function?
else if (typeof PAYLOAD === 'function' || typeof PAYLOAD.promise === 'function') {
promise = PAYLOAD.promise ? PAYLOAD.promise() : PAYLOAD();
data = PAYLOAD.promise ? PAYLOAD.data : undefined;
// Step 1.3.1: Is the return of action.payload a promise?
if (!(0, _isPromise2.default)(promise)) {
// If not, move on to the next middleware.
return next(_extends({}, action, {
payload: promise
}));
}
}
// Step 1.4: If there's no promise, move on to the next middleware.
else {
return next(action);
}
// Step 1b: If there's no payload, move on to the next middleware.
} else {
return next(action);
}
/**
* Instantiate and define constants for:
* (1) the action type
* (2) the action meta
*/
var TYPE = action.type;
var META = action.meta;
var otherAction = (0, _lodash.omit)(action, ['type', 'meta', 'payload']);
/**
* Instantiate and define constants for the action type suffixes.
* These are appended to the end of the action type.
*/
var _PROMISE_TYPE_SUFFIXE = _slicedToArray(PROMISE_TYPE_SUFFIXES, 3),
_PENDING = _PROMISE_TYPE_SUFFIXE[0],
_FULFILLED = _PROMISE_TYPE_SUFFIXE[1],
_REJECTED = _PROMISE_TYPE_SUFFIXE[2];
/**
* Function: getAction
* Description: This function constructs and returns a rejected
* or fulfilled action object. The action object is based off the Flux
* Standard Action (FSA).
*
* Given an original action with the type FOO:
*
* The rejected object model will be:
* {
* error: true,
* type: 'FOO_REJECTED',
* payload: ...,
* meta: ... (optional)
* }
*
* The fulfilled object model will be:
* {
* type: 'FOO_FULFILLED',
* payload: ...,
* meta: ... (optional)
* }
*/
var getAction = function getAction(newPayload, isRejected) {
return _extends({
// Concatentate the type string property.
type: [TYPE, isRejected ? _REJECTED : _FULFILLED].join(PROMISE_TYPE_DELIMITER)
}, newPayload === null || typeof newPayload === 'undefined' ? {} : {
payload: newPayload
}, META !== undefined ? { meta: META } : {}, isRejected ? {
error: true
} : {}, otherAction);
};
/**
* Function: handleReject
* Calls: getAction to construct the rejected action
* Description: This function dispatches the rejected action and returns
* the original Error object. Please note the developer is responsible
* for constructing and throwing an Error object. The middleware does not
* construct any Errors.
*/
var handleReject = function handleReject(reason) {
var rejectedAction = getAction(reason, true);
dispatch(rejectedAction);
throw reason;
};
/**
* Function: handleFulfill
* Calls: getAction to construct the fullfilled action
* Description: This function dispatches the fulfilled action and
* returns the success object. The success object should
* contain the value and the dispatched action.
*/
var handleFulfill = function handleFulfill() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var resolvedAction = getAction(value, false);
dispatch(resolvedAction);
return { value: value, action: resolvedAction };
};
/**
* First, dispatch the pending action:
* This object describes the pending state of a promise and will include
* any data (for optimistic updates) and/or meta from the original action.
*/
next(_extends({
// Concatentate the type string.
type: [TYPE, _PENDING].join(PROMISE_TYPE_DELIMITER)
}, data !== undefined ? { payload: data } : {}, META !== undefined ? { meta: META } : {}));
/**
* Second, dispatch a rejected or fulfilled action and move on to the
* next middleware.
*/
return promise.then(handleFulfill, handleReject);
};
};
};
}