redux-promise-middleware
Advanced tools
Comparing version 2.3.3 to 3.0.0
'use strict'; | ||
exports.__esModule = true; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
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 _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"); } }; }(); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
exports.default = promiseMiddleware; | ||
@@ -15,4 +19,11 @@ var _isPromise = require('./isPromise'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var defaultTypes = ['PENDING', 'FULFILLED', 'REJECTED']; | ||
/** | ||
* @function promiseMiddleware | ||
* @description | ||
* @returns {function} thunk | ||
*/ | ||
function promiseMiddleware() { | ||
@@ -23,19 +34,27 @@ var config = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
return function (_ref) { | ||
var dispatch = _ref.dispatch; | ||
return function (ref) { | ||
var dispatch = ref.dispatch; | ||
return function (next) { | ||
return function (action) { | ||
if (!_isPromise2['default'](action.payload)) { | ||
if (action.payload) { | ||
if (!(0, _isPromise2.default)(action.payload) && !(0, _isPromise2.default)(action.payload.promise)) { | ||
return next(action); | ||
} | ||
} else { | ||
return next(action); | ||
} | ||
// Deconstruct the properties of the original action object to constants | ||
var type = action.type; | ||
var payload = action.payload; | ||
var meta = action.meta; | ||
var promise = payload.promise; | ||
var data = payload.data; | ||
var _ref2 = (meta || {}).promiseTypeSuffixes || promiseTypeSuffixes; | ||
// Assign values for promise type suffixes | ||
var _ref = (meta || {}).promiseTypeSuffixes || promiseTypeSuffixes; | ||
var _ref2 = _slicedToArray(_ref, 3); | ||
var PENDING = _ref2[0]; | ||
@@ -46,45 +65,96 @@ var FULFILLED = _ref2[1]; | ||
/** | ||
* Dispatch the first async handler. This tells the | ||
* reducer that an async action has been dispatched. | ||
* @function getAction | ||
* @description Utility function for creating a rejected or fulfilled | ||
* flux standard action object. | ||
* @param {boolean} Is the action rejected? | ||
* @returns {object} action | ||
*/ | ||
next(_extends({ | ||
type: type + '_' + PENDING | ||
}, !!data ? { payload: data } : {}, !!meta ? { meta: meta } : {})); | ||
var isAction = function isAction(resolved) { | ||
return resolved && (resolved.meta || resolved.payload); | ||
}; | ||
var isThunk = function isThunk(resolved) { | ||
return typeof resolved === 'function'; | ||
}; | ||
var getResolveAction = function getResolveAction(isError) { | ||
var getAction = function getAction(newPayload, isRejected) { | ||
return _extends({ | ||
type: type + '_' + (isError ? REJECTED : FULFILLED) | ||
}, !!meta ? { meta: meta } : {}, !!isError ? { error: true } : {}); | ||
type: type + '_' + (isRejected ? REJECTED : FULFILLED) | ||
}, newPayload ? { | ||
payload: newPayload | ||
} : {}, !!meta ? { meta: meta } : {}, isRejected ? { | ||
error: true | ||
} : {}); | ||
}; | ||
/** | ||
* Re-dispatch one of: | ||
* 1. a thunk, bound to a resolved/rejected object containing ?meta and type | ||
* 2. the resolved/rejected object, if it looks like an action, merged into action | ||
* 3. a resolve/rejected action with the resolve/rejected object as a payload | ||
* Assign values for promise and data variables. In the case the payload | ||
* is an object with a `promise` and `data` property, the values of those | ||
* properties will be used. In the case the payload is a promise, the | ||
* value of the payload will be used and data will be null. | ||
*/ | ||
action.payload.promise = promise.then(function () { | ||
var resolved = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var promise = void 0; | ||
var data = void 0; | ||
var resolveAction = getResolveAction(); | ||
return dispatch(isThunk(resolved) ? resolved.bind(null, resolveAction) : _extends({}, resolveAction, isAction(resolved) ? resolved : _extends({}, !!resolved && { payload: resolved }))); | ||
}, function () { | ||
var rejected = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
if (!(0, _isPromise2.default)(action.payload) && _typeof(action.payload) === 'object') { | ||
promise = payload.promise; | ||
data = payload.data; | ||
} else { | ||
promise = payload; | ||
data = null; | ||
} | ||
var resolveAction = getResolveAction(true); | ||
return dispatch(isThunk(rejected) ? rejected.bind(null, resolveAction) : _extends({}, resolveAction, isAction(rejected) ? rejected : _extends({}, !!rejected && { payload: rejected }))); | ||
/** | ||
* First, dispatch the pending action. This flux standard action 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({ | ||
type: type + '_' + PENDING | ||
}, !!data ? { payload: data } : {}, !!meta ? { meta: meta } : {})); | ||
/** | ||
* Second, dispatch a rejected or fulfilled action. This flux standard | ||
* action object will describe the resolved state of the promise. In | ||
* the case of a rejected promise, it will include an `error` property. | ||
* | ||
* In order to allow proper chaining of actions using `then`, a new | ||
* promise is constructed and returned. This promise will resolve | ||
* with two properties: (1) the value (if fulfilled) or reason | ||
* (if rejected) and (2) the flux standard action. | ||
* | ||
* Rejected object: | ||
* { | ||
* reason: ... | ||
* action: { | ||
* error: true, | ||
* type: 'ACTION_REJECTED', | ||
* payload: ... | ||
* } | ||
* } | ||
* | ||
* Fulfilled object: | ||
* { | ||
* value: ... | ||
* action: { | ||
* type: 'ACTION_FULFILLED', | ||
* payload: ... | ||
* } | ||
* } | ||
*/ | ||
return new Promise(function (resolve, reject) { | ||
promise.then(function () { | ||
var value = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; | ||
var resolvedAction = getAction(value, false); | ||
dispatch(resolvedAction); | ||
resolve({ value: value, action: resolvedAction }); | ||
return; | ||
}, function () { | ||
var reason = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; | ||
var rejectedAction = getAction(reason, true); | ||
dispatch(rejectedAction); | ||
reject({ reason: reason, action: rejectedAction }); | ||
return; | ||
}); | ||
}); | ||
return action; | ||
}; | ||
}; | ||
}; | ||
} | ||
module.exports = exports['default']; | ||
} |
'use strict'; | ||
exports.__esModule = true; | ||
exports['default'] = isPromise; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
exports.default = isPromise; | ||
function isPromise(value) { | ||
if (value !== null && typeof value === 'object') { | ||
return value.promise && typeof value.promise.then === 'function'; | ||
if (value !== null && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object') { | ||
return value && typeof value.then === 'function'; | ||
} | ||
} | ||
module.exports = exports['default']; | ||
return false; | ||
} |
{ | ||
"name": "redux-promise-middleware", | ||
"version": "2.3.3", | ||
"description": "Redux middleware for handling promises", | ||
"version": "3.0.0", | ||
"description": "Redux middleware for handling promises and optimistic updates", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"pretest": "`npm bin`/eslint ./src/*.js", | ||
"test": "mocha --compilers js:babel/register --reporter spec test/*.js", | ||
"prepublish": "npm test && make js" | ||
"pretest": "`npm bin`/eslint src/**/*.js test/**/*.js", | ||
"start": "babel-node examples/server.js", | ||
"test": "mocha --compilers js:babel-core/register --reporter spec test/polyfills.js test/*.js", | ||
"build": "`npm bin`/babel src -d dist", | ||
"prepublish": "npm test && npm run build" | ||
}, | ||
@@ -18,4 +20,9 @@ "repository": { | ||
"middleware", | ||
"middlewares", | ||
"promise", | ||
"promises" | ||
"promises", | ||
"optimistic update", | ||
"optimistic updates", | ||
"async", | ||
"example" | ||
], | ||
@@ -32,18 +39,36 @@ "author": "Patrick Burtchaell <patrick@pburtchaell.com> (pburtchaell.com)", | ||
"devDependencies": { | ||
"babel": "^5.6.14", | ||
"babel-eslint": "^3.1.23", | ||
"chai": "^3.4.0", | ||
"eslint": "^0.24.1", | ||
"eslint-config-airbnb": "0.0.6", | ||
"eslint-plugin-react": "^2.7.0", | ||
"babel-cli": "^6.6.5", | ||
"babel-core": "^6.7.2", | ||
"babel-eslint": "^6.0.0", | ||
"babel-loader": "^6.2.4", | ||
"babel-polyfill": "^6.7.4", | ||
"babel-preset-es2015": "^6.6.0", | ||
"babel-preset-react": "^6.5.0", | ||
"babel-preset-stage-0": "^6.5.0", | ||
"chai": "^3.5.0", | ||
"eslint": "^2.5.1", | ||
"eslint-config-airbnb": "^6.2.0", | ||
"eslint-plugin-react": "^4.2.3", | ||
"express-urlrewrite": "^1.2.0", | ||
"faker": "^3.1.0", | ||
"isomorphic-fetch": "^2.2.1", | ||
"json-server": "^0.8.9", | ||
"lodash": "^4.6.1", | ||
"mocha": "^2.3.3", | ||
"react": "^0.14.7", | ||
"react-dom": "^0.14.7", | ||
"react-redux": "^4.4.1", | ||
"react-router": "^2.0.1", | ||
"redux": "^3.0.4", | ||
"redux-mock-store": "0.0.2", | ||
"redux-thunk": "^2.0.1", | ||
"sinon": "^1.17.2", | ||
"sinon-chai": "^2.8.0" | ||
"sinon-chai": "^2.8.0", | ||
"webpack": "^1.12.14", | ||
"webpack-dev-middleware": "^1.5.1", | ||
"webpack-hot-middleware": "^2.10.0" | ||
}, | ||
"peerDependencies": { | ||
"redux": "^2.0.0 || ^3.0.0" | ||
}, | ||
"dependencies": {} | ||
} | ||
} |
112
README.md
@@ -5,109 +5,17 @@ # Redux Promise Middleware | ||
# Getting Started | ||
Redux promise middleware enables robust handling of async code in [Redux](http://redux.js.org). The middleware enables optimistic updates and dispatches pending, fulfilled and rejected actions. It can be combined with [redux-thunk](https://github.com/gaearon/redux-thunk) to chain async actions. | ||
Install with npm: `npm i redux-promise-middleware -S` | ||
## Docs and Help | ||
## Usage | ||
- [Guides](/docs/guides/) | ||
- [Introduction](/docs/introduction.md) | ||
- [Example](/example) | ||
- [Releases](https://github.com/pburtchaell/redux-promise-middleware/releases) | ||
First, import the middleware and include it in `applyMiddleware` when creating the Redux store: | ||
**Older versions:** | ||
```js | ||
import promiseMiddleware from 'redux-promise-middleware'; | ||
- [2.x](https://github.com/pburtchaell/redux-promise-middleware/tree/2.2.4) | ||
- [1.x](https://github.com/pburtchaell/redux-promise-middleware/tree/1.0.0) | ||
composeStoreWithMiddleware = applyMiddleware( | ||
promiseMiddleware() | ||
)(createStore); | ||
``` | ||
To use the middleware, dispatch a promise within the `payload` of the action and specify a `type` string. You may pass an optional `data` object. This is dispatched from the pending action and is useful for optimistic updates. | ||
The pending action is dispatched immediately with the original type string and a suffix of `_PENDING`. The fulfilled action is dispatched only if the promise is resolved, e.g., if it was successful; and the rejected action is dispatched only if the promise is rejected, e.g., if an error occurred. The fulfilled and rejected suffixes are `_FULFILLED` and `_REJECTED` respectively. If necessary, it is possible [to change the value](#type-suffix-configuration) of the type suffixes. | ||
```js | ||
export function myAsyncActionCreator(data) { | ||
return { | ||
type: 'ACTION', | ||
payload: { | ||
promise: doSomethingAsyncAndReturnPromise(data), | ||
data: data | ||
} | ||
}; | ||
} | ||
``` | ||
The middleware returns a [FSA compliant](https://github.com/acdlite/flux-standard-action) action for both rejected and resolved/fulfilled promises. In the case of a rejected promise, an `error` is returned. You can access the promise of the action with `payload.promise`. This is useful for chaining actions together or using `async...await` within an action creator. | ||
## What is the difference between this and other promise middleware? | ||
In issue [#27](https://github.com/pburtchaell/redux-promise-middleware/issues/27), it was asked if this middleware is the same as [acdlite/redux-promise](https://github.com/acdlite/redux-promise). The short answer is that while the middleware solve the same problem, the implementation is different. | ||
The major difference is this middleware dispatches a `_PENDING` action. The pending action enables optimistic updates and provides an action one can use to update the user interface to inform the user a request is being made. This is a feature that acdlite/redux-promise has not implemented at time of writing this (November 2015). A similarity is that both middleware use the [Flux Standard Action](https://github.com/acdlite/flux-standard-action) specification. | ||
One could also argue the API for this middleware is more transparent and easier to integrate, e.g., you do not need to use [redux-actions](https://github.com/acdlite/redux-actions). | ||
## Dispatching actions when promises are resolved | ||
Often times when a promise is resolved, one might want to fire a one or more "callback" actions to respond to the resolved promise. One example is changing the route after a user is successfully signed in. | ||
If you need to do this, you can dispatch a second action: | ||
```js | ||
const actionCreator = () => ({ | ||
type: 'FIRST_ACTION_TYPE', | ||
payload: { | ||
promise: Promise.resolve({ | ||
type: 'SECOND_ACTION_TYPE' | ||
payload: ... | ||
}) | ||
} | ||
}); | ||
``` | ||
It is also possible to use a function: | ||
```js | ||
const actionCreator = () => ({ | ||
type: 'FIRST_ACTION_TYPE', | ||
payload: { | ||
promise: Promise.resolve((action, dispatch, getState) => { | ||
dispatch({ type: 'SECEOND_ACTION_TYPE', payload: ... }) | ||
dispatch(someActionCreator()) | ||
}) | ||
} | ||
}); | ||
``` | ||
Note that this behavior uses thunks, so you will need to include [thunk middleware](https://github.com/gaearon/redux-thunk) in your middleware stack. | ||
## Type suffix configuration | ||
When adding the promise middleware to your middleware stack, you can supply an optional configuration object. This object accepts an array of suffix strings that can be used instead of the default `['PENDING', 'FULFILLED', 'REJECTED']` with a key of `promiseTypeSuffixes`. | ||
```js | ||
applyMiddleware( | ||
promiseMiddleware({ | ||
promiseTypeSuffixes: ['LOADING', 'SUCCESS', 'ERROR'] | ||
}) | ||
) | ||
``` | ||
Alternatively, you can supply the same options at the action level inside the meta options that will change these suffixes on a per action type basis. | ||
```js | ||
export function myAsyncActionCreator(data) { | ||
return { | ||
type: 'ACTION', | ||
payload: { | ||
promise: doSomethingAsyncAndReturnPromise(data), | ||
data: data | ||
}, | ||
meta: { | ||
promiseTypeSuffixes: ['PENDING', 'H*LL_YEAH', 'SH*T'] | ||
} | ||
}; | ||
} | ||
``` | ||
--- | ||
Licensed MIT. Copyright 2015 Patrick Burtchaell. | ||
Licensed MIT. Copyright 2015-current Patrick Burtchaell. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
285195
123
6245
30
21
51
3