最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - How to make a JavascriptReactTypescript fetch call asynchronous? - Stack Overflow

programmeradmin0浏览0评论

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 ponent 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.

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 ponent 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.

Share Improve this question asked Nov 29, 2019 at 18:47 aaronaaarona 37.4k45 gold badges145 silver badges194 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 3

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:

  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).

发布评论

评论列表(0)

  1. 暂无评论