I'm trying to fetch some data from a database, and add it to an array, and I'm doing this inside of componentDidMount so that i can later use it to set the initial state of my component. However, while the array is updated inside the promise, it doesn't seem to be updated outside. Any ideas? Here is the code for reference. I can include more if necessary.
class Equations extends React.Component {
state = {
equations: []
}
}
componentDidMount() {
let equations = []
db.collection("equations").get().then(snapshot => {
snapshot.forEach(function(doc) {
equations = [...equations, doc.data()]
});
console.log(`inside: ${equations.length}`)
});
console.log(`outside: ${equations.length}`)
}
render() {
return <div>stuff</div>
}
My database currently has two entries in the databse collection, so of course I would expect both logs to return 2. But only the inside log returns 2; the outside log returns 0 for some reason.
That happens because the outside console.log is called before the inside, since it's a promise and it's asynchronous.
If you add async/await you will see that the outside will be updated.
async componentDidMount() {
let equations = []
await db.collection("equations").get().then(snapshot => {
snapshot.forEach(function(doc) {
equations = [...equations, doc.data()]
});
console.log(`inside: ${equations.length}`)
});
console.log(`outisde: ${equations.length}`)
}
The inside log is inside of a promise, which means it is asynchronous. Without diving into the code, the then is your giveaway that it's a promise. It 'waits' for your data API to finish before returning a response, and then it logs the equations.
The outside log is not inside a promise, which means it is synchronous. As soon as componentDidMount is called (immediately after the component is mounted) it logs the equations, which will be an empty list.
Any activities that depend on the response from the database should be inside your promise's response block.
Related
I have the following endpoint in a class called UserApi.js:
const controller = 'User';
...
export async function getEmployeeInfo(employeeId)
{
const query = createQueryFromObject({employeId});
const response = await get(`/${controller}/EmployeeInfo?${query}`);
return retrieveResponseData(response, []);
}
This is going to get the required information from an action method in the backend of UserController.cs.
Now, say that I want to display this information in EmployeeView.vue class, do I have to await it again? Why or why not? Initially, I would say no, you don't, as you already dealt with the await/async in the UserApi.js class, but what about the Promise.resolve? Please explain.
methods: {
async setReportData(
employeeId
) {
this.isBusy = true;
Promise.resolve(getEmployeeInfo(
employeeId
)).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Update:
....
* #param {Object} response
* #param {any} defaultData
* #param {Function} predicate
* #returns {Promise}
*/
export function retrieveResponseData(response, defaultData = null, predicate = (predicateResponse) => predicateResponse) {
const data = predicate(response) ? response.data : null;
return data || defaultData;
}
You need to await it since a function declared with async keyword ALWAYS returns a Promise, even if you do only synchronous stuff inside of it, hence you need to await or "thenize" it to access the value it resolved to. That depends from the implementation details of async functions which are just generators that yield promises.
If this concerns you because you work inside sync modules and don't like to use async functions just to execute more async functions, there's a good news, TOP-LEVEL await MODULES proposal is at stage 4, so it'll very soon be shipped with the next ECMA version. This way you will be able to await inside modules as if they were wrapped by async functions !
https://github.com/tc39/proposal-top-level-await
I can't tell if you need to await it again, because I can't tell what retrieveResponseData does. It might take the resolved value and wrap it in a fresh promise, which would then require callers of getEmployeeInfo to await the result.
Here's the why:
A Promise is a wrapper around a value
await unwraps a Promise. So does the .then() handler you can register with a Promise (but the value is only unwrapped within the function you provide to .then()).
Just like a gift in the real world, once something has been unwrapped, you don't need to unwrap it again. However, very conveniently for us, you can still use await on a value that is not wrapped in a Promise, and it will just give you the value.
You can wrap any value in a Promise, like so:
let wrappedFive = Promise.resolve(5)
//> wrappedFive is a Promise that must be unwrapped to access the 5 inside it
// this does _exactly_ the same thing as the above
let wrappedFive = new Promise(resolve => {
resolve(5)
})
Sometimes you end up in a situation where you can't use await, because you're in a function that cannot be marked async. The lifecycle methods of front-end frameworks like React (and possibly Vue) are like that: the framework needs each lifecycle method to do its job and be done immediately. If you mark the lifecycle method as async, you can often prevent it from having the intended effect.
In cases like that, you often need to use chained .then() handlers, which is a little uglier, but it works:
componentDidMount() {
// this API call is triggered immediately by lifecycle,
// but it basically starts a separate thread -- the rest
// of this function does not wait for the call to finish
API.getUserInfo()
.then(userInfo => {
// this happens after the API call finishes, but
// componentDidMount has already finished, so what happens
// in here cannot affect that function
this.setState({ username: userInfo.username })
})
// this happens immediately after the API call is triggered,
// even if the call takes 30 seconds
return 5
}
Note that a Promise does not actually start a separate thread -- these all happen in the same thread that executes the lifecycle method, i.e. the browser's renderer thread. But if you think of the codepath that executes, a Promise that you don't wait for basically introduces a fork into that codepath: one path is followed immediately, and the other path is followed whenever the Promise resolves. Since browserland is pretty much a single-threaded context, it doesn't really hurt you to think of creating a Promise as spawning a separate thread. This is a nuance you can ignore until you are comfortable with asychronous patterns in JS.
Update: retrieveResponseData does not appear to return a Promise. I could be wrong, if predict returns a Promise, but if that were true, then the ternary would always return response.data because unwrapped Promises are truthy values (even Promise.resolve(false) is truthy).
However, anyone who calls getEmployeeInfo will have to wait it, because that function is marked async, and that means it returns a Promise even if nothing inside it is is asynchronous. Consider this extreme example:
// this function returns a number
function gimmeFive() {
return 5
}
// this function returns a Promise wrapped around a number
async function gimmeFive() {
return 5
}
Async function getEmployeeInfo awaits the result of the get call in order to return the value returned by a call to retrieveResponeData.
Assuming neither get nor retrieveResponeData errors, the value syntactically returned in the body of getEmployeeInfo is used to resolve the promise object actually returned by calling getEmployeeInfo.
Promise.resolve is not needed to convert the result of calling getEmployeeInfo into a promise because, given async functions return promises, it already is.
It doesn't matter if retrieveResponseData returns a promise or not: standard async function processing waits for a returned promise value to be settled before resolving the promise returned when calling the async function.
Async function setRreportData is declared as async but written using chained promise handler methods to process data and error conditions in order - the async declaration could be omitted, but may be useful if modifications are made.
The results can only be used to update the page at a time when the data has finished being obtained and extracted, shown as a comment in the following code:
setReportData( employeeId) {
this.isBusy = true;
getEmployeeInfo(
employeeId
).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
// At this point the result in this.reportDatatableEmployeeInfo can be used to update the page
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Displaying the data using EmployeeView.vue class must wait until the data is available. The simplest place to insert updating the page (in the posted code) is within the then handler function inside setReportData.
Displaying the data
As posted setReportData does not notify its caller of when report data is available, either by means of a callback or by returning a promise. All it does is save the result in this.reportDatatableEmployeeInfo and dies.
Without using callbacks, setReportData is left with two choices
Return a promise that is fulfilled with, say,resultsEmployeeInfo or undefined if an error occurs:
setReportData( employeeId) {
this.isBusy = true;
// return the final promise:
return getEmployeeInfo(
employeeId
)
.then( (resultsEmployeeInfo) =>
(this.reportDatatableEmployeeInfo = resultsEmployeeInfo)
)
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
// return undefined
})
.finally(() => {
this.isBusy = false;
});
},
which could be used in a calling sequence using promises similar to
if(!this.busy) {
this.setReportData(someId).then( data => {
if( data) {
// update page
}
}
If you wanted to make the call in an async context you could use await:
if(!this.busy) {
const data = await this.setReportData(someId);
if( data) {
// update page
}
}
Update the page from within setReportData after the data becomes available ( as shown as a comment in the first part of this answer). The method should probably be renamed from setReportData to getReportData or similar to reflect its purpose.
Using Angular and the RxJS library.
I have a simple method:
method(){
const data = this.data;
const moreData = this.moreData;
this.anotherMethodOne(data)
this.anotherMethodTwo(moreData)
}
As you can see I am calling two other methods. anotherMethodOne() should be executed before anotherMethodTwo() - and it is.
Within anotherMethodOne() I have a .subscribe:
anotherMethodOne(data) {
<!-- at this point it jumps out of this method and continues to proceed in the parent method, but returns later -->
this.service.getService(id).subscribe(res => {
res
}
}
As soon as my debugger hits the .subscribe call it jumps back into the parent method() and proceeds to execute this.anotherMethodTwo(moreData). It then jumps back into this.anotherMethodOne(data) to finally complete the .subscribe - however, by this time its too late.
I need to make sure this.anotherMethodOne(data) is completed fully before this.anotherMethodTwo(moreData) runs or ensure that the .subscribe runs before it proceeds any further.
I started looking at switchMap and concat in the RxJS library, but unsure how that would be implemented in my case.
You cannot guarantee an observable would've emitted before proceeding with it's emitted value. So you need to return the observable from the methods and subscribe where it's value is required.
For observable depending on the emissions of other observables, you could use higher order mapping operator like switchMap. More info about them here.
method(){
const data = this.data;
const moreData = this.moreData;
this.anotherMethodOne(data).pipe(
switchMap((someValue) =>
this.anotherMethodTwo(moreData)
)
).subscribe({
// callbacks
});
}
anotherMethodOne(data): Observable<any> {
return this.http.get('url');
}
anotherMethodTwo(moreData): Observable<any> {
return this.http.get('url');
}
When it comes to asynchronous functions, you should stay with the reactive approach and not try to determine when a certain code has finished, but instead just wait for it to finish and then invoke the callback or the responsible handler subsequently. Think about this. What is the subscribe never returns anything - is that okay? What if it takes a really long time to return anything - is that okay too?
But to fix the problem at hand, simply move anotherMethodTwo into the subscription. Then when something is returned, anotherMethodTwo is run.
method(){
const data = this.data;
const moreData = this.moreData;
this.anotherMethodOne(data)
}
anotherMethodOne(data) {
this.service.getService(id).subscribe(res => {
console.log(res)
this.anotherMethodTwo(moreData)
}
}
Going down this path would be similar to async callback hell which nesting style is not recommended but it will do for now.
I currently used
asyncData for getting api data , but it can only used in pages ( not in components) .
But method can used in page and component .
These two method work the same ways and , so I am thinking to use methods for getting api data .
So I wonder is there any significant between asyncData and method ?
export default {
async asyncData ({ req }) {
let { data } = await axios.get(process.env.API_SERVER + `/v1/projects`)
return { items: data }
},
data () {
return {
items: null
}
},
methods: {
async getItems () {
let { data } = await axios.get(process.env.API_SERVER + `/v1/projects`)
this.items = data
}
}
There is a very big difference:
asyncData is a method which gets automatically called before the component gets initialized and therefore before the component data gets set.
Therefore you won't have access to this like in your methods.
asyncData is important for server side rendering where you want to fetch first your data before your component gets rendered with the fetched data. Nuxt will wait until the data got fetched before initializing and then rendering the component. Otherwise it would be rendered empty.
Methods are first available when the component is initialized.
You'll find more about asyncData here in the docs and there it's good described.
its like automatic promise
once you (ajax) request something then you get promise so you add then handler so when you get data your then code will be executed.
so ajax request will take some time so we are making that flow as async means continue the flow but when data received i need to execute some code which i have provided in then function
same goes with asyncData(its just wrapper for data with async capabilities) and async method what ever code you write inside will await for the given operation then when that operation is finished method will be executed.
its just like alert('hello') which await user for to click ok then continue the flow.
as well in server-side rendering it work same it will stop execution flow for a while for incoming data then again resumes it.
it more on depth with this js generators answer (if you are more interested): Difference between async/await and ES6 yield with generators
I have a service in an Angular2 project that takes some parameters and returns a value list to populate drop down menus on a form. When the form component initializes, I need to call the same service multiple times with different parameters to define a number of different dropdown menus, however if I call them all, the last one called clobbers the previous ones, presumably because the subsequent calls are overriding or cancelling the previous fetches.
I've split each of the calls into their own function, but I need a way to call each function in series so that the second doesn't get called until after the first completes. Each function works on its own, however if I call more than one, only the last one succeeds, and the first fail with errors (as the service itself terminates the current fetch when its called with new parameters before finishing).
this.fetchValueListOne();
this.fetchValueListTwo();
this.fetchValueListThree();
I was trying to make this work with promises, but wound up in scoping hell pretty quickly with having to pass the services I wanted to access into the functions and then not being able to get the resulting data back out again - each service call takes three parameters and then sets a specific this.valueList[] variable defined in the component and used on the form.
I also tried creating a list of the functions as variables and then iterating over them, however I ran into the same scoping issues as with promises.
The service returns an Observable, the functions subscribe to that Observable, retrieve the data and assign it to a array variable in the component that a dropdown value list is bound to.
The functions look like this:
fetchValueListOne() {
this.dataSvc.getValueList('Val-List-One', this.stateSvc.currentContext, this.stateSvc.currentLanguageCode)
.map(response => response.json())
.subscribe(
data => {
this.valListOne = data;
},
err => console.log('Error', err),
() => {
console.log('this.valListOne', this.valListOne);
}
);
}
SrAxi pointed me in the right direction, and ultimately I solved the problem as follows where Promises turned out to be the best solution, specifically the Promise / .then mechanism solved the problem nicely.
fetchValueList(listCode): Promise<any> {
return this.dataSvc.getValueList(listCode, this.stateSvc.currentContext, this.stateSvc.currentLanguageCode)
.map(response => response.json())
.toPromise();
}
initializeDropDowns() {
this.fetchValueList('First-Val-List')
.then(data => {
this.firstValList = data;
return this.fetchValueList('Second-Val-List')
}).then(data => {
this.secondValList = data;
return this.fetchValueList('Third-Val-List')
}).then(data => {
this.thirdValList = data;
}) }
I defined the functions in the component, and then called initializeDropDowns() in ngOnInit.
The fetchValueList function returns a Promise, so the first call passes the first listCode and when the Promise resolves, the return value is in the data variable in the .then block where we can assign it to the this.firstValList variable. As the function has returned data, we know the service has finished and it's safe to call again with the second listCode, the return value is in the data variable in the next .then block and we assign it to the this.secondValList variable.
We can chain this as many times as required to populate all the variables, and on the last code block we simply omit the return statement and the block terminates.
This is a very specific use case where we have a single service that needs to be called multiple times as the component initializes, and where the service has to complete its fetch and return a value before it can be called again, but in this case, the Promise / .then method was ideal.
Call the functions when you received the data. Such as:
this.fetchValueListOne().subscribe((firstData) => {
this.fetchValueListTwo(firstData);
// Do something with firstData
}
);
this.fetchValueListTwo().subscribe((secondData) => {
this.fetchValueListThree(secondData);
// Do something with secondData
}
);
this.fetchValueListThree().subscribe((thirdData) => {
// Do something with thirdData
}
);
And declare these functions as Observable, such as:
public fetchValueListOne(): Observable<any> { // Get firstData }
public fetchValueListTwo(): Observable<any> { // Get secondData}
public fetchValueListThree(): Observable<any> { // Get thirdData}
This way you will be certain that when you call a function you have the data from the previous one.
I'm using React Starter Kit to create my first React app, and I'm struggling with Ajax calls. I saw that this Kit embeds a way to perform Ajax calls (which is by the way used internally for app routing):
import fetch from '../../core/fetch';
I've added this in my component, and then try to perform an Ajax call when the component loads. Here is my code:
componentDidMount() {
var moduleManager = 'https://my_domain.com/my_endpoint';
async function getModules (url) {
const response = await fetch(url);
const content = await response.json();
return content;
};
this.state.modulesList = getModules(moduleManager);
console.log(this.state.modulesList);
}
I'm also using the state value in my render function:
render() {
var rows = [];
for (var i = 0; i < this.state.modulesList.length; i++) {
rows.push(
<li>{this.state.modulesList[i]}<li/>
);
}
This code put together logs this in my console:
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Then the Ajax call is performed (successfully) and my console is now showing this:
Promise
__proto__:Promise
[[PromiseStatus]]:"resolved"
[[PromiseValue]]:Array[16]
The desired behaviour is of course to update my view: when the ajax calls is performed, display one line per array member.
What am I missing?
Thanks
What I suggest doing:
constructor() {
...
// Bind your async function to your stateful component's class
this.getModules = this.getModules.bind(this);
}
async getModules(url) {
try {
// Perform the ajax call
const response = await fetch(url);
// Convert respone to json
const content = await response.json();
// Process your content (if needed)
...
// Call setState() here
this.setState({someContent: content});
} catch(e) {
console.error(e);
}
}
componentDidMount() {
this.getModules(`${URL}`);
}
You can't actually return the fetched/parsed data from an async function. According to this MDN link, async function returns a promise, and not the actual data you'd expect to get after parsing it.
What happened in your case, was that you were actually trying to receive a returned value from an async function, inside a regular(sync) function (componentDidMount). You can either do what I suggested, or use .then() to use setState after resolving and parsing the promise, in the actual componentDidMount function.
I suggest reading about async functions and Promise before continuing.
Best of luck!
Without testing your code, one problem is that you're incorrectly modifying state directly. That doesn't trigger a render and therefore your view is not updated. Try setState() instead, like so:
<li>{this.setState({modulesList[i]})}<li/>