React: this is not defined in async callback function - javascript

I have been on this for long now please I need assistance.
Am trying to make a call to an API link and I have an array of data to make the call as parameters to the call.
And After the call has been made I want to set the component state to the result gotten.
let animals = ['cats','goats'] ;
async.each(animals, function(item, cb){
axios.get(`http://api.com?keyword=${item}`)
.then(res=> {
apiData.push(res.data)
this.setState({
stateData: apiData
});
});
})

async.each only makes sense if you want to execute one request after another, and then you have to call cb() so that the chain continues:
async.each(array, (item, cb) => { // <- arrow func!
axios.get(`/http://api.com?keyword=${item}`)
.then(res => {
apiData.push(res.data);
this.setState({ stateData: apiData });
cb(); // <---
});
});
Or to execute all in parallel (which is probably much faster):
const promises = array.map(item => axios.get(`/http://api.com?keyword=${item}`).then(res => res.data));
Promise.all(promises).then(stateData => {
this.setState({ stateData });
});
PS: You should always handle errors in a promise, so just attach a .catch(/*..*/) to the chain ...,

Use arrow function for both forEach and api request callback, that will prevent javascript re-assigning the value of this during your callback chain.
More info about arrow functions in ES6 read: https://codeburst.io/javascript-arrow-functions-for-beginners-926947fc0cdc

Related

How to stack promises dynamically?

I have a nested object subprojects with a property of type array: userEstimates holding object(s) Estimate.
I am looking to iterate through userEstimates and push a fetch/promise to an array without calling it.
main (inside async function)
await subproject.getUserEstimates(true);
let stack = [];
subproject.userEstimates.forEach(ue =>
stack.push(ue.fetchAvailableRates)
);
console.log(stack); // 3) [ƒ, ƒ, ƒ]
await Promise.all(stack);
fillForm(subproject);
however, attributes on subproject are not defined when calling fillForm
function definition for fetchAvailableRates:
fetchAvailableRates = () => {
this.availableRates = fetch(...)
.then((resp) => {
if (resp.ok) return resp.json();
else throw new Error("Something went wrong");
})
.then((r) => {
...
return {
Rate.get(), // returns class
...
};
})
.catch((e) => {
console.error(e);
});
};
EDIT: Changed my wording frrom queue to stack as i'm trying to run all requests at once, and I don't care about order
Your definition for fetchAvailableRates uses this, but when you refer to an object's function without calling it (like stack.push(ue.fetchAvailableRates)), it loses the reference to this and simply becomes a function.
To refer to a function that calls ue.fetchAvailableRates on the ue instance at the time, you should call either () => ue.fetchAvailableRates() or ue.fetchAvailableRates.bind(ue).
That's not the only change you'd need to make―Promise.all() doesn't accept functions, only promises, so the right call is probably to make ue.fetchAvailableRates() return the Promise and add that to the stack.

Refactor nested async callback within react’s componentDidMount() using an external file

I’m using react’s componentDidMount(), which holds a lot of code and does two callbacks. Eventually, in the inner most callback (i.e., the second one), I execute this.setState({}).
Minimal code
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: {} };
}
requests() {
fooCallback.request({ data: { query: userQuery } }).subscribe(({ jsonResp1 }) => {
// 100 lines of data processing with jsonResp1
// second Callback
barCallback.request({ data: { string: userString } }).subscribe(({ jsonResp2 }) => {
// 200 lines of data processing with jsonResp2
this.setState({ data: processedData })
});
});
componentDidMount() {
this.requests();
}
}
render() {
// ...
return (
// ...
);
}
}
Since the request()method is very huge it bloats my main container component App. I’ve already tried to create an external js file (default export function ...) and imported that in my App.js. Yet, this was not possible probably due to the asynchron nature of the method.
Is it somehow possible to reduce my App.js?
Edit
What I tried in many variants is to create an external.js file:
external.js
export default async () => {
return fooCallback.request({ data: { query: userQuery } }).subscribe(({ jsonResp1 }) => {
// 100 lines of data processing with jsonResp1
// second Callback
barCallback.request({ data: { string: userString } }).subscribe(({ jsonResp2 }) => {
// 200 lines of data processing with jsonResp2
return processedData
});
return barCallback;
});
... and then to import it
App.js
import getData from './js/getData.js'
// ...
async componentDidMount() {
const response = await this.getData()
this.setState({data: response})
}
Yet, no success. Is the only way to create a class component, maybe?
If I understand your question correctly, you want to move the request function to a separate file, but within the function you use "this.setState" which is out of the components scoping.
You're right to move this logic out of the component. Callbacks are a little confusing to work with sometimes, especially when callbacks depend on other callbacks, so on and so forth.
Wouldn't it be nice if callbacks acted synchronous? You can simulate this by wrapping the callbacks in Promises and awaiting the results of the callbacks to resolve. This way, you're program will wait for the first callback to execute entirely and return data before executing the following callback (you were going to nest) that needed the output of the first.
Utility file will look something like:
function processRespOne(fooCallback, userQuery) {
return new Promise((resolve, reject) => {
fooCallback.request({ data: { query: userQuery } }).subscribe(({ jsonResp1 }) => {
// ...process jsonResp1
// by resolving this data, it acts like a standard function "return" if the invoking expression awaits it
resolve(processedData);
});
});
}
function processRespTwo(respOneData, barCallback, userString) {
return new Promise((resolve, reject) => {
barCallback.request({ data: { string: userString } }).subscribe(({ jsonResp2 }) => {
// process jsonResp2 with the output of respOneData
resolve(processedData);
});
});
}
// here I define the function "async" so i can use the "await" syntax, so my asynchronous function acts synchronous
// Async functions return promises. So caller's can await this function.
export async function requests(fooCallback, userQuery, barCallback, userString) {
const respOneData = await processRespOne(fooCallback, userQuery);
const results = await processRespTwo(respOneData, barCallback, userString)
// anyone who is "await"ing this function will get back results
return results;
}
In App.js
import { requests } from './js/getData.js';
//...
async componentDidMount() {
const results = await requests(fooCallback, userQuery, barCallback, userString);
this.setState({ results });
}
Here's a link to a really good answer discussing converting callbacks into promises for more synchronous like execution. Definitely take a look at the "nodebacks" example in the accepted answer: How do I convert an existing callback API to promises?

Asserting a callback was called with Jest (without React)?

I have a suite of tests but something is just not clicking regarding callback assertions. My feeling is that the done() parameter needs to be woven in, but I'm doing it wrong.
I basically have two function structures, where the callback is either nested inside of a single then statements, or a then inside of another then:
function foo(cb) {
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
.catch(err => console.error(err))
}
and
function foo(cb) {
return fetch('foo.com')
.then(()=>cb())
.catch(err => console.error(err))
}
I'm looking to assert that the callback was called in both cases.
I have tried
describe('ugh why can't I figure this out'?, () => {
it('is confusing', (done) => {
const testCb = jest.fn()
foo(testCb);
expect(testCb).toHaveBeenCalled()
done();
//failed: expected testCb to be called, but it was not called
}
})
I'm not sure how to move forward- I'm not a fan of the spaghetti on the wall approach, so I'd rather understand how to implement jest for testing async code before I just start switching in different syntax. The above code seems like it should work because the callback is part of the function execution, so if the higher order function executes, the callback would necessarily be executed, and should have been "called". Obviously this reasoning is wrong since the test isn't passing, but I'm not quite seeing the way around this.
Thanks very much for any direction/insight :).
This jest example seems to match mine- what am I missing?
describe('drinkAll', () => {
test('drinks something lemon-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});
test('does not drink something octopus-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});
You're expect is being called before the fetch comes back. Do this:
foo(testCb)
.then(_ => {
expect(testCb).toHaveBeenCalled();
done();
})
.catch(err => {
done.fail(err);
});
By chaining on to the Promise returned by foo we ensure the fetch has come back. Once you go async you have to stay that way, you can't mix sync and async code like you did in your posted code:
const testCb = jest.fn()
foo(testCb); // this can take an arbitrary amt of time
expect(testCb).toHaveBeenCalled() // but this happens immediately
done();
FWIW you can also change this
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
Into this:
return fetch('foo.com')
.then((res)=> res.json())
.then(cb)
.catch((err) => ...
The extra level of nesting promises is unnecessary and makes the code hard to read.

Using promises in React to wait for functions to finish within JavaScript map function

I've been trying to figure out what the proper way would be to write a promise for this function. I have an asynchronous function that makes an HTTP request to the server to retrieve a response, "documents_fileUploader." I am mapping through the "url" of each item within the response, and each url will go in to a function that makes another HTTP request and then sets the state. I want to fire the "upload()" function only after everything within the "documents_fileUploader()" function is complete. I tried doing this without a promise and it went straight to my "upload()" function because request was still pending. Any suggestions on how to go about this?
documents_fileUploader(formData).then(resp => {
resp.data.items.map(url => {
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
this.getFileObject(key);
})
}).then(() => {
this.upload();
})
getFileObject = file => {
file_view(file).then(resp => {
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}
To your main question, you can wait for every promise that your .map call returns by using the Promise.all method.
Second, in order for that to work, your getFileObject function must return the promise it creates.
So incorporating those two changes, your snippet might look like:
documents_fileUploader(formData).then(resp => {
return Promise.all(resp.data.items.map(url => { // Wrap in Promise.all and return it
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
return this.getFileObject(key); // Make sure to return this promise as well.
}));
}).then(() => {
// Now this won't happen until every `getFileObject` promise has resolved...
this.upload();
})
getFileObject = file => {
return file_view(file).then(resp => { // And return the promise here.
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}

Why is my react/redux function not returning a promise?

I am in my cart actions and I am calling loadCartItems() from another function within the same file. However, this function is not returning a promise or any data and I do not know why. My loadCartItems() function is not even being recongized as a function actually. Does anyone know why this might be?
export function loadCartItems() {
return (dispatch, getState) => {
dispatch({
type: types.LOAD_CART_PRODUCTS
});
return AsyncStorage.getItem(STORAGE_KEY_JWT_TOKEN).then((key) => {
return API.getCartItems(key)
.then((response) => {
return dispatch({
type: types.LOAD_CART_PRODUCTS_SUCCESS,
response
});
}).catch(err => {
console.log('Error retrieving cart products');
})
}).catch(err => {
console.log("Error retrieving cart items from local storage");
});
};
}
export function getUnaddedCartItems() {
return (dispatch, getState) => {
dispatch({
type: types.GET_UNADDED_ITEMS
});
return AsyncStorage.getItem(STORAGE_KEY_CART_ITEMS).then((result) => {
const addedItems = JSON.parse(result);
loadCartItems()
.then((result) => {
const cartItems = result.response.products;
if (this.state.unaddedCartItems.length === 0) {
const unaddedCartItems = addedItems.filter((addedItem) => {
return cartItems.find(cartItem => cartItem.id !== addedItem.productId);
});
}
}).catch(err => {
consoel.log('error: ', err);
});
}).catch(error => {
console.log('error: ', error);
});
};
}
this.loadCartItems() isn't a function. loadCartItems() is.
Since they aren't in a common class/object/something, there is no need to use this. It basically acts like a global (within the context of that file), so just call it directly.
Looking at it a bit closer, it looks like you are trying to call an action creator within an action creator. That's your problem.
Normally, you map these actions within your component, so it takes care of the dispatch bit for you. However, when you are calling the function directly yourself, you need to also deal with it yourself.
loadCartItems().then is the thing that isn't a function now that you've removed the this. That makes sense, since loadCartItems() actually returns a function, not a Promise. The function accepts two arguments: dispatch and getState.
You need to call it like this: loadCartItems()(dispatch, getState).then().
It doesn't actually say loadCartItems() is not a function
It says loadCartItems(...).then is not a function.
What does it mean?
In fact, loadCartItems(...).then is not a function, because the function doesn't return a Promise. It returns another function!
As stated in redux-thunk docs:
Any return value from the inner function will be available as the return value of dispatch itself.
So, in order to properly call your loadCartItems() action, you should do
dispatch(loadCartItems(anyParamYouWant)).then(...)
I'd recommend you to take a look at redux-thunk docs to help you get a better understanding on how thunks works ;)
Both loadItems and getUnaddedCartItems return thunks, not promises.
They therefore need to be dispatched first, so that they return the promises you're expecting.
That dispatch code before you return the promise seems to be unnecessary so if you in fact don't need it, just have the functions return the promises.

Categories