I am using redux / thunk to make async calls and use promise to resolve them.
Everything works fine but I am trying to figure out a way to resume on error (even if one api call fails).
Initial Load (Server side rendering)
export const initialLoad = () => {
return dispatch => Promise.all([
dispatch(loadResults()),
dispatch(loadGroups()),
]).catch(function (err) {
});
};
Functions:
function loadGroups() {
return dispatch =>
fetch(
url
).then(function (response) {
return response.json();
}).then(function (json) {
dispatch(setGroupData(json))
});
function loadResults() {
return dispatch =>
fetch(
url
).then(function (response) {
return response.json();
}).then(function (json) {
dispatch(setResultData(json))
});
It there a way to resume rest of the calls even though one of the api calls fails?
Related
I'm new in JS world and callbacks.
Why I can't return response after then function for the Firebase callable functions?
It returns empty if I return like shown below. I guess it doesn't wait for the response, response has data.output variable actually.
exports.testApi = functions.https.onCall(async(data, context) => {
const formData = new FormData();
formData.append("height", "512");
const response = await axios.post('https://....', formData, {
headers: formData.getHeaders()
})
.then((response) => {
console.log(response.data);
return {'imageURL':response.data.output};
})
.catch((error) => {
console.log(error)
});
}
);
It works with this format
const response = await axios.post('https://..', formData, {
headers: formData.getHeaders()
})
return {'imageURL':response.data.output};
The main attraction of async and await is cleaner syntax, in particular syntax that doesn't use then (opinion). You can mix them but I would always try to avoid it, especially here where the task is so simple.
exports.testApi = functions.https.onCall(async(data, context) => {
try {
const formData = new FormData();
formData.append("height", "512");
// Wait for post to give us a response.
const response = await axios.post('https://....', formData, {
headers: formData.getHeaders()
});
// If we get here, we waited and got a response. Proceed.
console.log(response.data);
return {'imageURL': response.data.output};
} catch (error) {
// If we get here, post threw an error (assuming it throws).
// And we never executed any lines after const response = await...
console.log(error);
}
});
Side note: Firebase Cloud Functions have to be properly terminated and this function does not. For example, if post throws an error and control flows to the catch block then the function will simply timeout because we haven't returned a Promise or thrown a compliant error.
I am using React with Meteor. I have a method inside React component that calls Meteor.method (which is run on client as well):
// index.js
loginWithGoogle() {
console.log('1')
Meteor.call('auth.loginWithGoogle', {}, (err, res)=>{
console.log('5');
if (err) {
console.log(err) // line 16:
} else {
console.log('success');
// console.log(res);
// console.log('logged in with google callback in react yay:)')
}
});
}
In client side Meteor I have method:
// auth.js
Meteor.methods({
async 'auth.loginWithGoogle'(){
console.log('2')
let err = await Meteor.loginWithGoogle()
console.log('3');
console.log(err)
if (err) {
console.log('-1')
throw new Error(err);
}
console.log('4');
// Meteor.loginWithGoogle({
// // options
// }, (err) => {
// console.log('3')
// if (err) {
// console.log(err)
// throw new Meteor.Error(err)
// } else {
// console.log('4')
// // successful login!
// }
// });
}
});
Note: Meteor.loginWithGoogle is provided by accounts-google package.
When testing I was able to navigate to google sign in page, sign in, and redirect back to my app) and logs are then printed.
Here the commented code is old approach. Notice, I have console.log calls with numbers, the numbers indicate the order in which I expect code to be executed. The old method does not work at all, console.log('5') runs earlier than (3 and 4), due to asynchronous execution. Rewriting with async/await gives this:
index.js:12 1
auth.js:4 2
auth.js:6 3
auth.js:7 undefined
auth.js:12 4
index.js:14 5
index.js:16 errorClass {isClientSafe: true, error: 404, reason: "Method 'auth.loginWithGoogle' not found", details: undefined, message: "Method 'auth.loginWithGoogle' not found [404]", …}
So, from logs I can see that code is executed as i expected.
Inside auth.js:7 I have err == undefined, but inside index.js (react part) it is errorClass.
How do we deal with async code in Meteor methods?
I got it. Why I was using Meteor.methods on client? I could just use javascript function like that:
const loginWithGoogle = async () => {
console.log('2')
let err = await Meteor.loginWithGoogle()
console.log('3');
console.log(err)
if (err) {
console.log('-1')
throw new Error(err);
}
console.log('4');
}
export {
loginWithGoogle
}
I just use async function, it returns Promise.
Inside React I use async syntax too:
async loginWithGoogle() {
let err = await loginWithGoogle()
if (err){
console.log(err)
}else {
console.log('success')
}
}
Should HTTPS functions return asynchronous promises like realtime functions have to?
We haven't been returning in HTTPS functions (just using res.status.send etc), and it looks like firebase/function-samples aren't either. But the documentation is slightly ambiguous https://firebase.google.com/docs/functions/terminate-functions .
This works now in the latest Firebase:
exports.asyncFunction = functions.https.onRequest(async (request, response) => {
const result = await someAsyncFunction();
response.send(result);
});
HTTP functions currently do not respect returned promises - they require a sent result in order to terminate normally. If an HTTP function doesn't send a result, it will time out.
All other types of functions require a returned promise in order to wait for asynchronous work to fully complete.
If you don't have any async work to wait for, you can just return immediately.
These are the three cases outlined in the docs.
After much looking around , this is implementation with a Promise worked for me to return a value from a Google Cloud Function where the function needs to make a third-party asynchronous call :
exports.getSomeAccessToken = functions.https.onCall((data, context) => {
var dataStr = JSON.stringify(data, null, '\t');
console.log('ENTER [getSomeAccessToken], got dataStr: ' + dataStr);
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, gatewayResponse) {
var result = {
clientToken: gatewayResponse.clientToken
};
var resultStr = JSON.stringify(result, null, '\t');
console.log("resultStr : " + resultStr);
resolve(result);
});
});
});
Your cloud functions should return"end" with either of the following
res.redirect(), res.send(), or res.end()
What they mean by returning promises, is lets imagine you have a cloud function that updated a node in your realtime database, you would like to complete that work before responding to the HTTP request.
Example code
let RemoveSomething = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Remove something
DoDatabaseWork()
.then(function (result) {
res.status(200).send();
})
.catch(function (err) {
console.error(err);
res.status(501).send();
});
});
});
Update: Added DoDatabaseWork example.
const DoDatabaseWork = function () {
return new Promise(function (resolve, reject) {
// Remove SomeNode
admin.database().ref('/someNode/').remove()
.then(function (result) {
resolve();
})
.catch(function (err) {
console.error(err);
reject();
});
});
}
I have an action, that uses a redux thunk, that looks like so:
export function fetchData(query) {
return dispatch => {
return fetch(`http://myapi?query=${query}` ,{mode: 'cors'})
.then(response => response.json())
.then(json => { dispatch(someOtherAction(json)) })
}
}
}
and then my someOtherAction actually updates state:
export function someOtherAction(data) {
return {
action: types.SOME_ACTION,
data
}
}
But i want it to be possible for the fetchData action creator to be reusable so that different parts of my app can fetch data from myapi and then have different parts of the state based on that.
I'm wondering what is the best way to reuse this action? Is it acceptable to pass a second parameter in to my fetchData action creator that stipulates which action is called on a successful fetch:
export function fetchData(query, nextAction) {
return dispatch => {
return fetch(`http://myapi?query=${query}` ,{mode: 'cors'})
.then(response => response.json())
.then(json => { dispatch(nextAction(json)) })
}
}
}
Or is there an accepted way of doing this sort of thing?
I use a middleware for that. I have defined the fetch call in there, then in my actions I send the URL to fetch and the actions to dispatch when completed. This would be a typical fetch action:
const POSTS_LOAD = 'myapp/POST_L';
const POST_SUCCESS = 'myapp/POST_S';
const POST_FAIL = 'myapp/POST_F';
export function fetchLatestPosts(page) {
return {
actions: [POSTS_LOAD, POST_SUCCESS, POST_FAIL],
promise: {
url: '/some/path/to/posts',
params: { ... },
headers: { ... },
},
};
}
When calling that action, the POST_LOAD action will be dispatch automatically by the middleware just before the fetch request it's executed. If everything goes well the POST_SUCCESS action will be dispatched with the json response, if something goes wrong the POST_FAIL action will be dispatched by the middleware.
All the magic it's in the middleware! And it's something similar to this:
export default function fetchMiddleware() {
return ({ dispatch, getState }) => {
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, actions, ...rest } = action;
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = actions;
next({ ...rest, type: REQUEST }); // <-- dispatch the LOAD action
const actionPromise = fetch(promise.url, promise); // <-- Make sure to add the domain
actionPromise
.then(response => response.json())
.then(json => next({ ...rest, json, type: SUCCESS })) // <-- Dispatch the success action
.catch(error => next({ ...rest, error, type: FAILURE })); // <-- Dispatch the failure action
return actionPromise;
};
};
}
This way I have all my requests on a single place and I can define the actions to run after the request it's completed.
------------EDIT----------------
In order to get the data on the reducer, you need to use the action name you defined on the original action creator. The following example shows how to handle the POST_SUCCESS action from the middleware to get the posts data from the json response.
export function reducer(state = {}, action) {
switch(action.type) {
case POST_SUCCESS: // <-- Action name
return {
...state,
posts: action.json.posts, // <-- Getting the data from the action
}
default:
return state;
}
}
I hope this helps!
I'm very bad when it comes to thinking of a title question, sorry for that.
My Problem:
I'm unit testing my async redux actions like it's suggested in the docs. I mock the API calls with nock and check for the dispatched actions with redux-mock-store. It works great so far, but I have one test that fails even though it clearly does work. The dispatched action neither does show up in the array returned by store.getActions() nor is the state changed in store.getState(). I'm sure that it does happen because I can see it when I test manually and observe it with Redux Dev Tools.
The only thing that is different in this action dispatch is that it is called in a promise in a catch of another promise. (I know that sounds confusing, just look at the code!)
What my code looks like:
The action:
export const login = (email, password) => {
return dispatch => {
dispatch(requestSession());
return httpPost(sessionUrl, {
session: {
email,
password
}
})
.then(data => {
dispatch(setUser(data.user));
dispatch(push('/admin'));
})
.catch(error => {
error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
};
}
This httpPost method is just a wrapper around fetch that throws if the status code is not in the 200-299 range and already parses the json to an object if it doesn't fail. I can add it here if it seems relevant, but I don't want to make this longer then it already is.
The action that doesn't show up is dispatch(setError(data.error)).
The test:
it('should create a SET_SESSION_ERROR action', () => {
nock(/example\.com/)
.post(sessionPath, {
session: {
email: fakeUser.email,
password: ''
}
})
.reply(422, {
error: "Invalid email or password"
})
const store = mockStore({
session: {
isFetching: false,
user: null,
error: null
}
});
return store.dispatch(actions.login(
fakeUser.email,
""))
.then(() => {
expect(store.getActions()).toInclude({
type: 'SET_SESSION_ERROR',
error: 'Invalid email or password'
})
})
});
Thanks for even reading.
Edit:
The setErroraction:
const setError = (error) => ({
type: 'SET_SESSION_ERROR',
error,
});
The httpPostmethod:
export const httpPost = (url, data) => (
fetch(url, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
})
.then(checkStatus)
.then(response => response.json())
);
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
};
Because of you are using nested async function in catch method - you need to return the promise:
.catch(error => {
return error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
Otherwise, dispatch will be called after your assertion.
See primitive examples:
https://jsfiddle.net/d5fynntw/ - Without returning
https://jsfiddle.net/9b1z73xs/ - With returning