Observable from 2 promises - redux

I think my solution is in this question but I can't get it to work Promise.all behavior with RxJS Observables?
I'm trying to return an observable on two promises via forkJoin.
One promise gets an ID from the server and another processes a file to generate a thumbnail.
export function createSceneFromFile(action$) {
return action$.ofType(CREATE_SCENE_FROM_FILE)
.mergeMap(({locationId,file}) =>
createSceneThumb(locationId,file)
.map((res,preview) => {
console.log(res,preview)
if (res.error) {
return { type: CREATE_SCENE_FAILED, payload: res.message }
} else {
return {type: CREATE_SCENE_SUCCESS, payload: {...res.payload,preview} }
}
})
.catch(err => { return { type: CREATE_SCENE_FAILED, message: err } })
)
}
function createSceneThumb(locationId,file){
const request = fetch(`${API_URL}/location/${locationId}/createscene/${file.name}/`, {
method: 'get',
credentials: 'include',
}).then(res => res.json())
const thumb = fileToScenePreview(file)
return Observable.forkJoin(request,thumb)
}
export function fileToScenePreview(file){
return new Promise((resolve,reject)=>{
getFileThumb(file).then((canvas)=> {
canvas.toBlob((blob) => {
blob.lastModifiedDate = new Date()
blob.name = 'scenepreview_' + file.name + '.png'
const uploader = new S3Upload({
getSignedUrl: getSignedUrl,
uploadRequestHeaders: {'x-amz-acl': 'public-read'},
contentType: 'image/png',
scrubFilename: (filename) => filename.replace(/[^\w\d_\-.]+/ig, ''),
contentDisposition: 'auto',
s3path: 'assets/',
onError:()=>reject,
onFinishS3Put: ()=>resolve(blob.name),
})
uploader.uploadFile(blob)
})
})
})
}
But i never get a response.
Is this the right way of going about it?

Related

Action call is returning 'undefined' with fetch-mock

Action "type": undefined, is what I keep getting returned when I try to test a redux action with fetch-mock. Any suggestion on how to resolve this issue? Could it be a bug in fetch-mock?
Expected value to equal:
[{"type": undefined}, {"result": {"hello": "world"}, "type": undefined}]
Received:
[{"type": "DASHBOARD_RESULT_LOADING"}, {"result": {"hello": "world"}, "type": "DASHBOARD_RESULT_READY"}]
dashboardActions.js
function resultReady(json) {
return {
type: DASHBOARD_RESULT_READY,
result: camelizeKeys(json)
};
}
export function requestPredict(params) {
let url = `${process.env.API_URL}/predict/`;
const requestParams = {
method: 'post',
credentials: 'include'
};
return async (dispatch) => {
return fetch(url, requestParams)
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw Error(response.statusText);
}
})
.then(data => dispatch(resultReady(data)));
};
}
dashboardActions.test.js
const mockData = {
"hello": "world"
}
describe('action creators', () => {
afterEach(() => {
fetchMock.reset()
})
it('should create DASHBOARD_RESULT_LOADING', () => {
fetchMock.post('*', {"hello":"world"} );
const expectedActions = [
{ type: actions.DASHBOARD_RESULT_LOADING },
{ type: actions.DASHBOARD_RESULT_READY, result: mockData }
]
const store = mockStore({ result: {}})
return store.dispatch(actions.requestPredict())
.then((data) => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
You're receiving types "DASHBOARD_RESULT_LOADING" and "DASHBOARD_RESULT_READY", which seems to be the intended behaviour. You're expecting actions.DASHBOARD_RESULT_LOADING and actions.DASHBOARD_RESULT_READY, neither of which seems to be defined with any value.
Either define actions.DASHBOARD_RESULT_LOADING and actions.DASHBOARD_RESULT_READY:
actions.DASHBOARD_RESULT_LOADING = 'DASHBOARD_RESULT_LOADING'
actions.DASHBOARD_RESULT_READY = 'DASHBOARD_RESULT_READY'
or replace them with your expected types:
const expectedActions = [
{
type: 'DASHBOARD_RESULT_LOADING'
},
{
type: 'DASHBOARD_RESULT_READY',
result: mockData
}
]

RxJs and redux-observable. Append value when ajax succeed or failed

I have the following epic:
export const changeTeamSubscriptionPlan = (braintreePlanId: string) => ({
type: CHANGE_TEAM_SUBSCRIPTION_PLAN,
braintreePlanId,
});
export const changeTeamSubscriptionPlanEpic = (action$: any, store: Store<ReduxState, *>) =>
action$.ofType(CHANGE_TEAM_SUBSCRIPTION_PLAN)
.mergeMap(({ braintreePlanId }) => {
const state = store.getState();
const { subscription_id } = state.payment.subscription;
let request;
if (subscription_id) {
request = ajax(api.changeTeamSubscriptionPlan(subscription_id, braintreePlanId));
} else {
const [method] = state.payment.paymentMethods;
request = ajax(api.createSubscription(braintreePlanId, method.token));
}
// I would like to emit another value ({ type: FETCH_TEAM }) no matter what happens
//(basically I try to invalidate the team data even if the request fails)
return request
.map(response => ({
type: CHANGE_TEAM_SUBSCRIPTION_PLAN + SUCCESS,
payload: {
data: response.response,
status: response.status,
},
}))
.catch(error => Observable.of({
type: CHANGE_TEAM_SUBSCRIPTION_PLAN + FAILURE,
response: {
data: error.xhr.response,
status: error.xhr.status,
},
}));
});
What I want to do is no matter if ajax call ends with catch or calls map I want to append another value.
I run out of ideas, so I'm hoping for help.
After switching to original operators it turned out that I can do this:
return request
.map(response => ({
type: CHANGE_TEAM_SUBSCRIPTION_PLAN + SUCCESS,
payload: {
data: response.response,
status: response.status,
},
}))
.catch(error => Observable.of({
type: CHANGE_TEAM_SUBSCRIPTION_PLAN + FAILURE,
error: mapToError(error),
}))
.concat(Observable.of({ type: 'CHUJ_TYPE' }));
and concat will append value even when the catch block fires.
I was originally using custom operator which I though will work just like catch does but will reduce boilerplate in my app:
Observable.prototype.mapFailure = function catchAndMapError(ACTION_TYPE, optionalPayload = {}) {
return Observable.create(subscriber =>
this.subscribe(
value => subscriber.next(value),
(error) => {
try {
const action = {
type: ACTION_TYPE + FAILURE,
error: {
...mapToError(error),
...optionalPayload,
},
};
subscriber.next(action);
} catch (e) { // catch mappings error
subscriber.error(e);
}
},
() => subscriber.complete(),
),
);
};
It seems like it doesn't work the same as catch.....

How to test cyclejs http driver?

Suppose I have an API that return user detail:
/api/get_user/1
{
"status": 200,
"data": {
"username": "username1",
"email": "username#email.com"
}
}
And a "main function" like this:
function main (sources) {
const request$ = sources.ACTIONS
.filter(action => action.type === 'GET_USER_REQUEST')
.map(action => action.payload)
.map(payload => ({
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}))
const action$ = sources.HTTP
.select('GET_USER_REQUEST')
.flatten()
.map(response => response.data)
const sinks = {
HTTP: request$,
LOG: action$
}
return sinks
}
For testing the "ACTION" source, I can simply made an xstream observable
test.cb('Test main function', t => {
const actionStream$ = xs.of({
type: 'GET_USER_REQUEST',
payload: { userId: 1 }
})
const sources = { ACTION: actionStream$ }
const expectedResult = {
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}
main(sources).HTTP.addEventListener({
next: (data) => {
t.deepEqual(data, expectedResult)
},
error: (error) => {
t.fail(error)
},
complete: () => {
t.end()
}
})
})
The question is. Is it possible to do the same thing (using plan xstream observable)
to test cycle-http driver without a helper from something like nock?
Or is there a better way to test something like this?
You can mock out the HTTP source like so:
test.cb('Test main function', t => {
const actionStream$ = xs.of({
type: 'GET_USER_REQUEST',
payload: { userId: 1 }
})
const response$ = xs.of({
data: {
status: 200,
data: {
username: "username1",
email: "username#email.com"
}
}
});
const HTTP = {
select (category) {
// if you have multiple categories you could return different streams depending on the category
return xs.of(response$);
}
}
const sources = { ACTION: actionStream$, HTTP }
const expectedResult = {
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}
main(sources).HTTP.addEventListener({
next: (data) => {
t.deepEqual(data, expectedResult)
},
error: (error) => {
t.fail(error)
},
complete: () => {
t.end()
}
})
})
Really, we should have a mockHTTPSource helper to make this a bit easier. I have opened an issue to that effect. https://github.com/cyclejs/cyclejs/issues/567
If you want to test that certain things happen at the correct time, you could use this pattern in conjunction with #cycle/time.
http://github.com/cyclejs/time

How can I get axios result first ,then send action ?

Here is origin code:
export function startGame() {
return function(dispatch) {
axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
})
.then(response => {
if(response.status===200){
dispatch({
type: TYPE.START_GAME,
});
}
})
.catch((error) => {
dispatch({
type: TYPE.ERROR,
});
});
}
}
what I want is I get the api result first, and then decide what next step I want to do (because I have many actions that all call the same api )
my logic is below, but I don't know how to make it work
Please help me
export function startGame() {
let result = function(dispatch) {
axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
})
.then(response => {
if(response.status===200){
return {
"result" : "OK",
"data" : response.data
}
}
})
.catch((error) => {
return {
"result" : "FAIL",
"data" : error
}
});
}
if result.result === "OK" {
dispatch(someAction())
}else{
dispatch(otherAction())
}
}
I'm not sure why you can't just dispatch the someAction and otherAction in your axios callbacks. Why doesn't this work for you?
export function startGame() {
return function(dispatch) {
axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
})
.then(response => {
if (response.status === 200) {
dispatch(someAction(response.data));
}
})
.catch((error) => {
dispatch(otherAction(error));
});
}
}
If you want to define the API calling function elsewhere, you can do this:
// In some other file, say api.js
export function startGameApiCall() {
return axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
});
}
// In your actions file
import { startGameApiCall } from './api';
export function startGame() {
return function (dispatch) {
startGameApiCall()
.then(response => dispatch(someAction(response.data)))
.catch(() => dispatch(otherAction()));
}
}
I would also look into https://github.com/svrcekmichal/redux-axios-middleware It dispatches another action depending on the result of you axios request.

Redux Chain Multiple Ajax Promises in one Action

I have a Redux action which needs to make 2 subsequent ajax calls.
The first calls googlemaps api: https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}
The second calls a local service based on those results
/api/content/stores/byDistance/${lat},${lng}/sort
I'm using superagent to do the ajax calls. Clearly I'm experience difficulties keeping track of the promises, and including failures.
Am I mis-undestanding a core concept of Promises? Is there a simpler way to write the below?
export function loadBySearch(searchTerm) {
const geoSearchUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}`;
return {
types: [LOAD, LOAD_BY_LAT_LONG, LOAD_FAIL],
//Do I need to make this promise here?
promise: (client) => {
const promise = new Promise( (resolve, reject) => {
console.info('making google geocode request', geoSearchUrl);
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then( (successData1) =>{
const results = successData1.body.results;
if (!results.length) {
reject(`no results found for this search : ${searchTerm}`);
return;
}
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
client.get(path).then(
(successData2) => {
resolve( {
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
},
(errorData2) => {
reject( {
searchTerm: searchTerm,
result: errorData2
});
},
);
},
(errorData1) => {
reject({
searchTerm: searchTerm,
result: errorData1
});
}
);
});
return promise;
}
};
}
I'm not using superagent, but I'm guessing something like this might just work:
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then(successData1 => {
const results = successData1.body.results;
if (!results.length) {
throw(`no results found for this search : ${searchTerm}`);
}
return Promise.resolve(results);
})
.then(results => {
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
return client.get(path);
})
.then(successData2 => {
return Promise.resolve({
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
})
.catch(error => {
return Promise.reject({
searchTerm: searchTerm,
result: error
});
});
Haven't test it, but I hope at least it helps ;)

Resources