我有以下代码:
export const myEpic = (action$, store) =>
action$.ofType("SOME_ACTION")
.switchMap(action => {
const {siteId, selectedProgramId} = action;
const state = store.getState();
const siteProgram$ = Observable.fromPromise(axios.get(`/url/${siteId}/programs`))
.catch(error =>{
return Observable.of({
type: 'PROGRAM_FAILURE'
error
});
});
const programType$ = Observable.fromPromise(axios.get('url2'))
.catch(error =>{
return Observable.of({
type: "OTHER_FAILURE",
error
});
});
到目前为止一切顺利,当我抓住它时出现错误,并且(可能这是错误的)将其映射到一个动作(表示失败了)。
现在问题开始了,我有另一个observable,它是上面两个observable的zip运算符的结果:
const siteProgram$result$ = Observable.zip(siteProgram$, programType$)
.map(siteProgramsAndProgramTypes => siteProgramsAndProgramTypesToFinalSiteProgramsActionMapper(siteProgramsAndProgramTypes, siteId));
问题是我仍然可以看到这一点,好像一切都很好。
有没有办法理解"其中一个"压缩"观察者的错误,然后没有到达"下一个" siteProgram $ result $。 我想我错过了一些微不足道的事情......
我不想要执行此检查:
const siteProgramsAndProgramTypesToFinalSiteProgramsActionMapper = (siteProgramsAndProgramTypesArray, siteId) => {
const [programsResponse, programTypesResponse] = siteProgramsAndProgramTypesArray;
if (programsResponse.error || programTypesResponse.error){
return {
type: 'GENERAL_ERROR',
};
}
每次我都有一个observable,它是其他observable操作符的结果,可能有错误。
在纯rxjs中(不在redux observable中)我想我可以订阅它传递一个对象
{
next: val => some logic,
error: err => do what ever I want :) //this is what I am missing in redux observable,
complete: () => some logic
}
// some more logic
return Observable.concat(programType$Result$, selectedProgramId$, siteProgram$result$);
在redux observable中攻击它的正确方法是什么?
感谢。
答案 0 :(得分:0)
这是一个使用API包装器的详细示例,以方便您尝试实现的目标。
要点可以在GitHub here
上找到这是封装Observable.ajax
的API包装器,允许您分派单个操作或操作数组,并处理源自Observable.ajax
import * as Rx from 'rxjs';
import queryString from 'query-string';
/**
* This function simply transforms any actions into an array of actions
* This enables us to use the synthax Observable.of(...actions)
* If an array is passed to this function it will be returned automatically instead
* Example: mapObservables({ type: ACTION_1 }) -> will return: [{ type: ACTION_1 }]
* Example2: mapObservables([{ type: ACTION_1 }, { type: ACTION_2 }]) -> will return: [{ type: ACTION_1 }, { type: ACTION_2 }]
*/
function mapObservables(observables) {
if (observables === null) {
return null;
} else if (Array.isArray(observables)) {
return observables;
}
return [observables];
}
/**
* Possible Options:
* params (optional): Object of parameters to be appended to query string of the uri e.g: { foo: bar } (Used with GET requests)
* headers (optional): Object of headers to be appended to the request headers
* data (optional): Any type of data you want to be passed to the body of the request (Used for POST, PUT, PATCH, DELETE requests)
* uri (required): Uri to be appended to our API base url
*/
function makeRequest(method, options) {
let uri = options.uri;
if (method === 'get' && options.params) {
uri += `?${queryString.stringify(options.params)}`;
}
return Rx.Observable.ajax({
headers: {
'Content-Type': 'application/json;charset=utf-8',
...options.headers,
},
responseType: 'json',
timeout: 60000,
body: options.data || null,
method,
url: `http://www.website.com/api/v1/${uri}`,
// Most often you have a fixed API url so we just append a URI here to our fixed URL instead of repeating the API URL everywhere.
})
.flatMap(({ response }) => {
/**
* Here we handle our success callback, anyt actions returned from it will be dispatched.
* You can return a single action or an array of actions to be dispatched eg. [{ type: ACTION_1 }, { type: ACTION_2 }].
*/
if (options.onSuccess) {
const observables = mapObservables(options.onSuccess(response));
if (observables) {
// This is only being called if our onSuccess callback returns any actions in which case we have to dispatch them
return Rx.Observable.of(...observables);
}
}
return Rx.Observable.of();
})
.catch((error) => {
/**
* This if case is to handle non-XHR errors gracefully that may be coming from elsewhere in our application when we fire
* an Observable.ajax request
*/
if (!error.xhr) {
if (options.onError) {
const observables = mapObservables(options.onError(null)); // Note we pass null to our onError callback because it's not an XHR error
if (observables) {
// This is only being called if our onError callback returns any actions in which case we have to dispatch them
return Rx.Observable.of(...observables);
}
}
// You always have to ensure that you return an Observable, even if it's empty from all your Observables.
return Rx.Observable.of();
}
const { xhr } = error;
const { response } = error.xhr;
const actions = [];
const resArg = response || null;
let message = null;
if (xhr.status === 0) {
message = 'Server is not responding.';
} else if (xhr.status === 401) {
// For instance we handle a 401 here, if you use react-router-redux you can simply push actions here to your router
actions.push(
replace('/login'),
);
} else if (
response
&& response.errorMessage
) {
/*
* In this case the errorMessage parameter would refer to SampleApiResponse.json 400 example
* { "errorMessage": "Invalid parameter." }
*/
message = response.errorMessage;
}
if (options.onError) {
// Here if our options contain an onError callback we can map the returned Actions and push them into our action payload
mapObservables(options.onError(resArg)).forEach(o => actions.push(o));
}
if (message) {
actions.push(showMessageAction(message));
}
/**
* You can return multiple actions in one observable by adding arguments Rx.Observable.of(action1, action2, ...)
* The actions always have to have a type { type: 'ACTION_1' }
*/
return Rx.Observable.of(...actions);
});
}
const API = {
get: options => makeRequest('get', options),
post: options => makeRequest('post', options),
put: options => makeRequest('put', options),
patch: options => makeRequest('patch', options),
delete: options => makeRequest('delete', options),
};
export default API;
以下是动作,动作创作者和史诗:
import API from 'API';
const FETCH_PROFILE = 'FETCH_PROFILE';
const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
const FETCH_PROFILE_ERROR = 'FETCH_PROFILE_ERROR';
const FETCH_OTHER_THING = 'FETCH_OTHER_THING';
const FETCH_OTHER_THING_SUCCESS = 'FETCH_OTHER_THING_SUCCESS';
const FETCH_OTHER_THING_ERROR = 'FETCH_OTHER_THING_ERROR';
function fetchProfile(id) {
return {
type: FETCH_PROFILE,
id,
};
}
function fetchProfileSuccess(data) {
return {
type: FETCH_PROFILE_SUCCESS,
data,
};
}
function fetchProfileError(error) {
return {
type: FETCH_PROFILE_ERROR,
error,
};
}
function fetchOtherThing(id) {
return {
type: FETCH_OTHER_THING,
id,
};
}
const fetchProfileEpic = action$ => action$.
ofType(FETCH_PROFILE)
.switchMap(({ id }) => API.get({
uri: 'profile',
params: {
id,
},
/*
* We could also dispatch multiple actions using an array here you could dispatch another API request if needed
* We can redispatch another action to fire another epic if we want also.
* In both onSuccess and onError you can return a single action, an array of actions or null
* Note here we fire fetchOtherThing(data.someOtherThingId) which will trigger our fetchOtherThingEpic!
* In this case the data parameter would refer to SampleApiResponse.json 200 example
* { firstName: "John", lastName: "Doe" }
*/
onSuccess: ({ data }) => [fetchProfileSuccess(data), fetchOtherThing(data.someOtherThingId)],
onError: error => fetchProfileError(error),
})
const fetchOtherThingEpic = action$ => action$.
ofType(FETCH_OTHER_THING)
.switchMap(({ id }) => API.get({
uri: 'other-thing',
params: {
id,
},
onSuccess: ...
onError: ...
});
以下是使用上述示例的数据示例:
/*
* If possible, you should standardize your API response which will make error/data handling a lot easier on the client side
* Note, this data format is to work with the example code above
*/
/**
* Status code: 401
* This error would be caught in the .catch() method of our API wrapper
*/
{
"errorMessage": "Please login to perform this operation",
"data": null,
}
/**
* Status code: 400
* This error would be caught in the .catch() method of our API wrapper
*
*/
{
"errorMessage": "Invalid parameter.",
"data": null
}
/**
* Status code: 200
* This would be passed to our onSuccess function specified in our API options
*/
{
"errorMessage": 'Please login to perform this operation',
"data": {
"firstName": "John",
"lastName": "Doe"
}
}