aarona
aarona

Reputation: 37344

How to make a Javascript/React/Typescript fetch call asynchronous?

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

Answers (4)

Frank
Frank

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:

  1. Run promise, and wait for it to resolve.
  2. Take the result from promise and pass it into func1. Wait for func1 to resolve.
  3. Take the result from func1 and pass it into func2. Wait for func2 to resolve.
  4. 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).

Upvotes: 2

Trace
Trace

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

Matt Aft
Matt Aft

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

Renaldo Balaj
Renaldo Balaj

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

Related Questions