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/>
Related
There is some data loaded with fetch(url) and a function handleKeyboardEvent() started with #HostListener:
async ngOnInit() {
this.loadedFromFileData = await this.getFileFromUrl('filename.wav');
}
#HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
//... wait until this.loadedFromFileData loaded
}
async getFileFromUrl(url: string) {
let response = await fetch(url);
let ab = await response.arrayBuffer();
return ab;
}
How do I make handleKeyboardEvent() wait until loadedFromFileData data is loaded?
Rather the use of Promise is being abandoned. Observables are much more convenient and offer many more possibilities. In this case, I suggest that the method returns Observable and subscribe to it, e.g. in ngOnInit() method. Thanks to the pipe() method, we can execute a series of instructions that are to happen before the end of our stream (Observable).
If we want to call an operation when we are sure that the data has already been downloaded and, for example, assigned to an object, we can perform such a function in the subscribe() method.
For your case I created an example that shows how you can launch eventListener only after downloading the data.
https://stackblitz.com/edit/angular-ivy-ptkexw?file=src/app/app.component.ts
Overall, the topic Observable is very broad and strongly related to RxJS because we use RxJS operators that allow us to modify, create, transform data, etc.
In the example on stackblitz, I added an operator that delays the download of data from the server by 5 seconds. Open the console, reload the page and press the button on the keyboard - you will see that the event does not appear in the console. First of all, the returned data will appear in the console after 5 seconds, and only then the event will react to it when you press the keys.
More info here:
https://angular.io/guide/observables
https://rxjs.dev/guide/observable
As #Pointy pointed out, you can call .then() in ngOnInit instead of await.
loadedFromFileData: ArrayBuffer;
message: string = 'no data available';
constructor() {}
ngOnInit() {
this.getFileFromUrl(
'filename.wav'
).then((res) => {
this.loadedFromFileData = res;
});
}
#HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
//... wait until this.loadedFromFileData loaded
console.log(this.loadedFromFileData.byteLength)
if (this.loadedFromFileData) {
this.message = 'data loaded';
}
}
async getFileFromUrl(url: string) {
let response = await fetch(url);
console.log(response)
let ab = await response.arrayBuffer();
return ab;
}
Working example on Stackblitz
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.
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.
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 am following redux-saga documentation on helpers, and so far it seems pretty straight forward, however I stumbled upon an issue when it comes to performing an api call (as you will see link to the docs points to such example)
There is a part Api.fetchUser that is not explained, thus I don't quiet understand if that is something we need to handle with libraries like axios or superagent? or is that something else. And are saga effects like call, put etc.. equivalents of get, post? if so, why are they named that way? Essentially I am trying to figure out a correct way to perform a simple post call to my api at url example.com/sessions and pass it data like { email: 'email', password: 'password' }
Api.fetchUser is a function, where should be performed api ajax call and it should return promise.
In your case, this promise should resolve user data variable.
For example:
// services/api.js
export function fetchUser(userId) {
// `axios` function returns promise, you can use any ajax lib, which can
// return promise, or wrap in promise ajax call
return axios.get('/api/user/' + userId);
};
Then is sagas:
function* fetchUserSaga(action) {
// `call` function accepts rest arguments, which will be passed to `api.fetchUser` function.
// Instructing middleware to call promise, it resolved value will be assigned to `userData` variable
const userData = yield call(api.fetchUser, action.userId);
// Instructing middleware to dispatch corresponding action.
yield put({
type: 'FETCH_USER_SUCCESS',
userData
});
}
call, put are effects creators functions. They not have something familiar with GET or POST requests.
call function is used to create effect description, which instructs middleware to call the promise.
put function creates effect, in which instructs middleware to dispatch an action to the store.
Things like call, put, take, race are effects creator functions. The Api.fetchUser is a placeholder for your own function that handles API requests.
Here’s a full example of a loginSaga:
export function* loginUserSaga() {
while (true) {
const watcher = yield race({
loginUser: take(USER_LOGIN),
stop: take(LOCATION_CHANGE),
});
if (watcher.stop) break;
const {loginUser} = watcher || {};
const {username, password} = loginUser || {};
const data = {username, password};
const login = yield call(SessionService.login, data);
if (login.err === undefined || login.err === null && login.response) {
yield put(loginSuccess(login.response));
} else {
yield put(loginError({message: 'Invalid credentials. Please try again.'}));
}
}
}
In this snippet, the SessionService is a class that implements a login method which handles the HTTP request to the API. The redux-saga call will call this method and apply the data parameter to it. In the snippet above, we can then evaluate the result of the call and dispatch loginSuccess or loginError actions accordingly using put.
A side note: The snippet above is a loginSaga that continuously listens for the USER_LOGIN event, but breaks when a LOCATION_CHANGE happens. This is thanks to the race effect creator.