New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Subscribe on Epic #90
Comments
With Epics, this probably an anti-pattern. non-trivial side effects would belong in your Epics but obviously react-router was not designed with redux in mind so there probably isn't an ideal solution. That said, it's possible to do this: onEnter(_, _, callback) {
const request = new Promise((resolve, reject) => {
dispatch(({
type: FETCH_SOMETHING
meta: { resolve, reject }
}))
});
request.then(() => {
callback();
});
} const fetchSomethingEpic = action$ =>
action$.ofType(FETCH_SOMETHING)
.mergeMap(action =>
api.fetchSomething() // whatever your API code is
// resolve/reject the promise
.do({
next: action.meta.resolve,
error: action.meta.reject
})
.map(fetchSomethingFulfilled)
.catch(error => fetchSomethingRejected(error.xhr.message)) // whatever your error handling
); I would use this sort of approach very sparingly. There may be other patterns that emerge later. |
One could write a middleware to handle this sort of thing magically. Untested, but something like this: const actionLifecycles = store => next => {
const pending = {};
return action => {
let ret;
if (action.meta && action.meta.lifecycle) {
ret = new Promise((resolve, reject) => {
const { lifecycle } = action.meta.lifecycle;
pending[lifecycle.resolve] = resolve;
pending[lifecycle.reject] = reject;
});
} else {
ret = next(action);
}
if (pending[action.type]) {
const resolveOrReject = pending[action.type];
resolveOrReject(action);
}
return ret;
};
}; Where you define what actions to listen for, to resolve or reject a promise. const fetchSomething = () => ({
type: FETCH_SOMETHING,
meta: {
lifecycle: {
resolve: FETCH_SOMETHING_FULFILLED,
reject: FETCH_SOMETHING_REJECTED
}
}
}); Allows you to use it like this: dispatch(fetchSomething()).then(action => {
// success
}, action => {
// failure
}); Where get more confusing is what about two requests of the same action concurrently? What about cancellation? |
One could write a middleware to handle this sort of thing magically. Untested, but something like this: const actionLifecyclesMiddleware = store => next => {
const pending = {};
return action => {
let ret;
if (action.meta && action.meta.lifecycle) {
ret = new Promise((resolve, reject) => {
const { lifecycle } = action.meta.lifecycle;
pending[lifecycle.resolve] = resolve;
pending[lifecycle.reject] = reject;
});
next(action);
} else {
ret = next(action);
}
if (pending[action.type]) {
const resolveOrReject = pending[action.type];
delete pending[action.type];
resolveOrReject(action);
}
return ret;
};
}; Where you define what actions to listen for, to resolve or reject a promise. const fetchSomething = () => ({
type: FETCH_SOMETHING,
meta: {
lifecycle: {
resolve: FETCH_SOMETHING_FULFILLED,
reject: FETCH_SOMETHING_REJECTED
}
}
}); Allows you to use it like this: dispatch(fetchSomething()).then(action => {
// success
}, action => {
// failure
}); Where it gets more confusing is what about two requests of the same action concurrently..and what about cancellation? |
I believe the question has been answered so going to close due to inactivity but absolutely feel free add any other questions here or ideally on Stack Overflow for SEO 💃 |
Thanks @jayphelps. Very handy for implementing libraries that expect a promise. For simple uses cases this should work great. |
I had the same issue and reimplemented @jayphelps's solution with some Typescript typings and it works like charm: import { Middleware } from 'redux';
export interface IActionLifecycle {
resolveType: string;
rejectType: string;
}
/**
* Middleware which allows to chain async actions as Promises.
* @see https://github.com/redux-observable/redux-observable/issues/90
* @example
* const fetchSomething = () => ({
* type: FETCH_SOMETHING,
* meta: {
* lifecycle: {
* resolve: FETCH_SOMETHING_FULFILLED,
* reject: FETCH_SOMETHING_REJECTED
* }
* }
* });
*
* Then you can use the action as following:
* fetchSomething().then(action => doSomething(action))
*/
const middleware: Middleware = (store) => (next) => {
const pending: { [key: string]: Function } = {};
return (action: IAction) => {
let returned;
if (action.meta && action.meta.lifecycle) {
returned = new Promise((resolve, reject) => {
const lifecycle: IActionLifecycle = action.meta.lifecycle;
const pendingResolves = pending[lifecycle.resolveType];
const pendingRejections = pending[lifecycle.rejectType];
pending[lifecycle.resolveType] = resolve;
pending[lifecycle.rejectType] = reject;
});
next(action);
} else {
returned = next(action);
}
// This part is called later when the success/error action is dispatched
if (pending[action.type]) {
const actionCallback = pending[action.type];
delete pending[action.type];
actionCallback(action);
}
return returned;
};
};
export default middleware; |
That is perfect for my project. Thank you i'll create a repository for help anyone want use it easily. For my solution i create middleware like in this issues : /**
* Create Middleware
**/
const observableToPromise = store => next => {
let pending = {}
return action => {
let ret = next(action)
if (action.meta && action.meta.lifecycle) {
ret = new Promise((resolve, reject) => {
pending[action.meta.lifecycle.resolve] = resolve
pending[action.meta.lifecycle.reject] = reject
})
next(action)
}
// Success/Error action is dispatched
if (pending[action.type]) {
const resolveOrReject = pending[action.type]
delete pending[action.type]
resolveOrReject(action)
}
return ret
}
}
export default observableToPromise
/**
* On configureStore dev and prod
**/
function configureStore (baseHistory) {
const routingMiddleware = routerMiddleware(baseHistory)
const middleware = applyMiddleware(
...,
observableToPromise // Add your observable here
)
/**
* Action Creator for fetching
**/
export function actionRequest (arg) {
return {
type: ACTION_LOADING,
payload: arg || {},
meta: {
lifecycle: {
resolve: ACTION_SUCCESS,
reject: ACTION_FAILED
}
}
}
}
export function actionSuccess (data) {
return {
type: ACTION_SUCCESS,
payload: {
results: data
}
}
}
export function actionFailed (error) {
return {
type: ACTION_FAILED,
payload: new Error('fetch:' + ACTION_FAILED, error),
error: error.message
}
}
/**
* Epic Observable
**/
import { Observable } from 'rxjs/Observable'
import { ACTION_LOADING } from '../constants/ActionTypes'
import {
actionSuccess,
actionFailed
} from '../actions/action'
import { apiUri } from '../../../config/app.config'
import { $http } from '../services/http'
export default function fetchAction (action$) {
return action$.ofType(ACTION_LOADING)
.mergeMap(action => Observable
.from(
$http.get(apiUri)
)
.map(
data => actionSuccess(data),
error => actionFailed(error)
)
.catch(error => Observable.of(actionFailed(error)))
)
} Like this i can use promise on my router react and chains my promises, workly perfectly. |
doesn't it occur error? I think it should be
or do I misunderstand something? |
@cannalee90 yeah that it not possible for this case. |
@ShineOfFire you mean am I wrong? I just want to know I understand this issue correctly because like you said, I want to chains my promises like this:
|
@cannalee90 it work just your props fetchGistAll is surrounded by a dispatch show me your code |
redux-observable에서 비동기 액션을 수행할 경우 특정 액션이 언제 끝나는지 모름. 간단한 방법으론 callback 함수를 같이 넣어주는 방식이 있는데, 코드가 지저분해진다. redux-observable/redux-observable#90 를 참조해서 작성했다. 다음에는 kesakiyo 버젼으로 적용해볼 예정
I was find problem in my code, the middleware is called 2 times if action is not a promise |
Hi!
Since trunkservables are deprecated, how one can dispatch and subscribe?
i.e. in
onEnter()
of react-router I want to load some data asynchronously with redux-observable and then callcallback
to load the view (i.e. if user is authorized)It's a bit unclear how to wait until Epic is completed (it was easy with trunkservables because they were returning an Observable)
Thanks!
The text was updated successfully, but these errors were encountered: