I'm putting my API calls in a new function so I can easily call them with a couple of lines instead of a ton of code on every page. The problem I'm having is that the async and await is completing before returning the data.
I'm getting console.log(sess.getIdToken().getJwtToken()) consoled first, followed by undefined for console.log(getMessages), then the data from console.log(response).
So the await Auth.currentSession().then(...) runs but does't wait for the axios call inside of it. How do I return the data so I can access it in the useEffect?
useEffect(() => {
async function getMessages() {
const getMessages = await ApiService.getMessages();
console.log(getMessages)
}
getMessages();
}, []);
async getMessages() {
return await this.__sendRequest("/default/message", "GET");
}
async __sendRequest(url, method) {
await Auth.currentSession().then(sess => {
console.log(sess.getIdToken().getJwtToken())
axios({
method: method,
url: process.env.REACT_APP_USER_URL + url,
headers: {
"X-TOKEN-ID": sess.getIdToken().getJwtToken(),
"addresseeType": "P",
"content-type": "application/json"
}
}).then(function (response) {
console.log(response)
return response
}).catch(err => {
console.error(err);
})
})
}
Mixing async/await with promise-chaining (.then(...)) is a really easy way how to overcomplicate your code.
I can see a couple of places that contribute to this not working.
In __sendRequest you are not returning anything.
in the first .then you are lacking a return too.
Here is a simplified and fixed code
async __sendRequest(url, method) {
const sess = await Auth.currentSession()
console.log(sess.getIdToken().getJwtToken())
const response = await axios({
method: method,
url: process.env.REACT_APP_USER_URL + url,
headers: {
"X-TOKEN-ID": sess.getIdToken().getJwtToken(),
"addresseeType": "P",
"content-type": "application/json"
}
})
console.log(response)
return response
}
Check the comments of the other answer, to understand about async/await, returning and call stack https://stackoverflow.com/a/69179763/1728166
There are a couple of issues:
__sendRequest has no return, so when it's done waiting it will fulfill its promise with undefined.
The promise from the chain on Auth.currentSession() isn't waiting for the axios call to completely because the two chains aren't linked in any way.
(Not the reason you're seeing the premature settlement, but still an issue.) Don't trap errors too early, let them propagate so the caller knows that something went wrong.
Also, in general, don't mix using .then/.catch with async/await, there's no need (generally); just use await:
useEffect(() => {
async function getMessages() {
try {
const getMessages = await ApiService.getMessages();
console.log(getMessages);
} catch (error) {
// ...*** handle/report error...
}
}
getMessages();
}, []);
async getMessages() {
return await this.__sendRequest("/default/message", "GET");
}
async __sendRequest(url, method) {
const sess = await Auth.currentSession();
console.log(sess.getIdToken().getJwtToken());
return await axios({
method: method,
url: process.env.REACT_APP_USER_URL + url,
headers: {
"X-TOKEN-ID": sess.getIdToken().getJwtToken(),
"addresseeType": "P",
"content-type": "application/json"
}
});
}
getMessages() is async so you need to call with await
and make the callback function of first line async so you can use await inside it.
useEffect(async () => {
async function getMessages() {
const getMessages = await ApiService.getMessages();
console.log(getMessages)
}
await getMessages();
}, []);
you can simplify the __sendRequest by converting all then() callbacks into await and put the whole chain inside a try/catch block.
Related
I have this function in my React App. It calls a few other functions. Everything works except, I need the .then(() => this.getHCAid()) to complete before .then(() => this.addDocsList()) runs. I have not been able to make that happens. I'm sure it's simple, I just don't know how.
createHCA() {
fetch(API_URL + `/hca/create`, {
method: "PUT",
body: JSON.stringify({
client: this.state.client,
short: this.state.short,
}),
headers: { "Content-Type": "application/json" },
})
.then((res) => {
if (!res.ok) {
throw new Error();
}
return res.json();
})
.then((data) => console.log(data))
.catch((err) => console.log(err))
.then(() => this.getHCAid()) <--- Need this to complete
.then(() => this.addDocsList()) <--- Before this runs
.then(() => this.getAllHCAs());
this.setState({ showHide: false});
}
I'd encourage you to do a couple of things here...
Use async/await rather than a myraid of .then promises memoize
these function calls and use a useEffect (or multiple) with
dependencies to control when side-effects should be triggered. Convert to functional component to achieve this result.
"showHide" is a very confusing term for future developers, be kind to
your future self and future devs and make a term like "show" or
"isHidden"
If you really want to continue this pattern (chained .then statements), put your .catch at the very bottom to handle any issues/errors. This is where async/await is worth the time to quickly learn.
You only want functions doing one main thing, and this is to createHCA, let your other code trigger the functions. As you scale you will be thankful you did.
async function createHCA() {
let data
try {
data = await fetch(API_URL + `/hca/create`, {
method: "PUT",
body: JSON.stringify({
client: this.state.client,
short: this.state.short,
}),
headers: { "Content-Type": "application/json" },
})
} catch e => {
throw new Error(e.message)
return
}
this.setState({ showHide: false});
return data?.json()
}
It sounds like there are two issues going on here.
The first is that your functions sound like database queries or updates which will be asynchronous so you can't rely on the next step in the cycle to have access to any data returned if it hasn't been told to wait.
The solution you tried to come up with, setting state in one of the then methods, won't work either because it too batches up queries and processes them asynchronously, and the next step won't have access to that data either.
So, ideally what you should probably do is use a promise to return the id from getHCAid, and then pass that into addDocsList which also returns a promise, etc.
Here's a simplified example using async/await.
getHCAid() {
return new Promise((res, rej) => {
// Do DB process, get an id
res(id);
});
}
async getData() {
const response = await fetch(API_URL);
const data = await response.json();
const id = await this.getHCAid();
const list = await this.addDocsList(id);
const hcas = await this.getAllHCAs();
this.setState({ showHide: false });
}
i am a beginner in javascript async await function. I have tried to make a asynchronous request in my backend and i want it to initialized in my variable but when I tried to log my variable, it gives me a promise and not a value. When i also tried to put an await. It gives me error.
Here is what I've done:
const getActivityContestApi = async () => {
try {
const response = await fetch(
`${getDjangoApiHost()}/api/events/event_activity_contests/`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
return data;
} catch(error) {
console.log(error)
}
}
The function above is my asynchronous function, I want this to be stored in a variable like this:
const test_variable = getActivityContestApi();
console.log(test_variable);
this code gives me a promise. I want the actual variable so I tried to put await like this:
const test_variable = await getActivityContestApi();
console.log(test_variable);
this gives me error in react. please help.
await is only allowed inside async function.
const test_variable = await getActivityContestApi();
you can only use this statement inside another async function.
asyncFunction will return a promise. So if you just want get result, maybe you can use then
getActivityContestApi().then((resolve,reject)=>{
let test_variable = resolve;
console.log(test_variable);
})
await can't exist outside of async function. Here is what you need to do:
;(async () => {
const test_variable = await getActivityContestApi();
console.log(test_veriable);
})()
My 2 API calls happen to be at the same time, where the response of API1 is to be sent as a request parameter to API2. But, the value goes as undefined because it isn't fetched till that time. Is there any way this can be solved in react.
There are multiple ways to solve this problem, I will explain one of the latest as well most sought after ways of solving the problem.
I am sure you would have heard of async/await in JavaScript, if you haven't I would suggest you to go through an MDN document around the topic.
There are 2 keywords here, async && await, let's see each of them one by one.
Async
Adding async before any function means one simple thing, instead of returning normal values, now the function will return a Promise
For example,
async function fetchData() {
return ('some data from fetch call')
}
If you run the above function in your console simply by fetchData(). You'd see that instead of returning the string value, this function interestingly returns a Promise.
So in a nutshell async ensures that the function returns a promise, and wraps non-promises in it.
Await
I am sure by now, you would have guessed why we use keyword await in addition to async, simply because the keyword await makes JavaScript wait until that promise (returned by the async function) settles and returns its result.
Now coming on to how could you use this to solve your issue, follow the below code snippet.
async function getUserData(){
//make first request
let response = await fetch('/api/user.json');
let user = await response.json();
//using data from first request make second request/call
let gitResponse = await fetch(`https://api.github.com/users/${user.name}`)
let githubUser = await gitResponse.json()
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
As you can see the above code is quite easy to read and understand. Also refer to THIS document for more information on async/await keyword in JavaScript.
Asyn/await solves your problem:
const requests = async () =>{
const response1 = await fetch("api1")
const result1 = await response1.json()
// Now you have result from api1 you might use it for body of api2 for exmaple
const response2 = await fetch("api2", {method: "POST", body: result1})
const result2 = await response1.json()
}
If you're using react hooks, you can use a Promise to chain your API calls in useEffect
useEffect(() => {
fetchAPI1().then(fetchAPI2)
}, [])
relevant Dan Abramov
fetch(api1_url).then(response => {
fetch(api2_url, {
method: "POST",
body: response
})
.then(response2 => {
console.log(response2)
})
})
})
.catch(function (error) {
console.log(error)
});
or if using axios
axios.post(api1_url, {
paramName: 'paramValue'
})
.then(response1 => {
axios.post(api12_url, {
paramName: response1.value
})
.then(response2 => {
console.log(response2)
})
})
.catch(function (error) {
console.log(error);
});
Consider the following Javascript/React code:
// Javascript function that has a fetch call in it.
export const signIn = (email:string, password:string) => {
console.log("FETCHING...");
fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
})
.catch((error) => {
console.error('ERROR: ', error)
})
console.log("DONE FETCHING...");
}
// A functional component that references signIn.
export const SignIn: React.FC<Props> = () => {
// irrelevant code ...
const onSubmit = (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
return <>A form here submits and calls onSubmit</>
}
This produces the following console log output:
SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...
I want FETCHED DATA... to show up before DONE FETCHING.... I've tried playing around with aysnc/await but that's not working so I don't know where to go from here.
Just add another .then
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
return
}).then(()=> {
console.log("DONE FETCHING...");
})
.catch((error) => {
console.error('ERROR: ', error)
})
It would have to be in the then statements if you want the console.log to wait until the promise is resolved. Here's an example that uses async/await:
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
const response = await fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await response.json();
console.log("FETCHED DATA...")
console.log("DONE FETCHING...");
}
You would also need to turn this into an async function if you want the console.log to happen after the data is done fetching:
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
await signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
In order to use async await, you need to return a promise from the call. So basically you don't execute the .then and wrap the call in a try catch block.
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
return fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
}
and
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
try {
const data = await signIn(email, password, setAuthentication, setCurrentUser)
// Parse data, do something with it.
console.log("SIGNED IN...");
} catch (e) {
// handle exception
}
}
You may want to look more into how promises in JavaScript works.
One problem here is in signIn. What you're doing right now is:
function signIn() {
// 1. log FETCHING
// 2. call asynchronous fetch function
// 3. log DONE FETCHING
}
The key here is that fetch is asynchronous. The program doesn't wait for it to finish before moving on. See the problem? The JavaScript interpreter is going to run step 3 without waiting for step 2 to finish.
There are multiple ways to fix this. First, you can use then. Here's an example:
promise
.then(res => func1(res))
.then(res => func2(res))
.then(res => func3(res))
Here, you're telling JavaScript to:
Run promise, and wait for it to resolve.
Take the result from promise and pass it into func1. Wait for func1 to resolve.
Take the result from func1 and pass it into func2. Wait for func2 to resolve.
etc.
The key difference here is that you are running each then block in order, waiting for each previous promise to be resolved before going to the next one. (Whereas before you didn't wait for the promise to resolve).
Your code with promises would look like:
export const signIn = (email: string, password: string) => {
console.log("FETCHING...")
// Note that we return the promise here. You will need this to get onSubmit working.
return fetch(/* args */)
.then(res => res.json())
.then(({ data }) => console.log("DONE FETCHING"))
.catch(err => /* HANDLE ERROR */)
}
The second way to fix this is to use async and await. async and await is simply syntax sugar over promises. What it does underneath is the exact same, so make sure you understand how promises work first. Here's your code with async and await:
// The async keyword here is important (you need it for await)
export const signIn = async (email: string, password: string) => {
console.log("FETCHING...");
try {
const res = await fetch(/* args */) // WAIT for fetch to finish
const { data } = res.json()
console.log("FETCHED DATA...")
} catch (err) {
/* HANDLE ERROR */
}
console.log("DONE FETCHING...")
}
There's also a second similar problem in onSubmit. The idea is the same; I'll let you figure it out yourself (the important part is that you must return a Promise from signIn).
i'm working with an API that allows me to sync data to a local DB. There is a syncReady API that I'm calling recursively until the sync batch is ready to start sending data. The recursion is working correctly and the .then callback is called, but the resolve function never resolves the response.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
return new Promise((res, rej) => {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
res(response);
}, (error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
this.syncReady(token, batchID);
} else rej(error);
}
);
});
};
I get the 497 logged and the "The Response: {"pagesTotal";0}" response but the res(response) never sends the response down the chain. I've added a console.log message along the entire chain and none of the .then functions back down the chain are firing.
I hope I've explained this well enough :-). Any ideas why the promise isn't resolving?
Thanks!
First, you don't need to wrap something that returns a promise with a new Promise. Second, for your error case you don't resolve the promise if it is 497.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
return request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
return response;
})
.catch((error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
return this.syncReady(token, batchID);
} else {
throw error;
}
})
);
};
Maybe something like the above will work for you instead. Maybe try the above instead. As a general rule of thumb, it's you almost always want to return a Promise.