Reputation: 37344
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.
Upvotes: 1
Views: 5150
Reputation: 418
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:
promise
, and wait for it to resolve.promise
and pass it into func1
. Wait for func1
to resolve.func1
and pass it into func2
. Wait for func2
to resolve.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
).
Upvotes: 2
Reputation: 18889
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
}
}
Upvotes: 3
Reputation: 8936
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...");
}
Upvotes: 3
Reputation: 2440
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)
})
Upvotes: 3