如何使 Javascript/React/Typescript 获取调用异步?

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

考虑以下 Javascript/React 代码:

// 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</>
}

这会产生以下控制台日志输出:

SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...

我希望 FETCHED DATA...DONE FETCHING... 之前出现。我试过玩弄 aysnc/await 但那不起作用所以我不知道从这里去哪里。

再加一个.then

  .then((response) => {
    return response.json()
  })
  .then(({ data }) => {
    console.log("FETCHED DATA...")
    return
  }).then(()=> {
      console.log("DONE FETCHING...");
  })
  .catch((error) => {
    console.error('ERROR: ', error)
  })

如果您希望 console.log 等到 promise 得到解决,它必须在 then 语句中。这是一个使用 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...");
}

如果您希望 console.log 在数据获取完成后发生,您还需要将其转换为 async 函数:

  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...");
  }

为了使用 async await,您需要 return 来自调用的承诺。所以基本上你不执行 .then 并将调用包装在 try catch 块中。

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

  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 
    }
  }

您可能想更多地了解 JavaScript 中的 promise 是如何工作的。

signIn 中有一个问题。你现在正在做的是:

function signIn() {
  // 1. log FETCHING
  // 2. call asynchronous fetch function
  // 3. log DONE FETCHING
}

这里的关键是fetch异步的。该程序不会等到它完成后再继续。看到问题了吗? JavaScript 解释器将进入 运行 第 3 步,而不等待第 2 步完成。

有多种方法可以解决这个问题。首先,您可以使用 then。这是一个例子:

promise
  .then(res => func1(res))
  .then(res => func2(res))
  .then(res => func3(res))

在这里,你告诉 JavaScript 到:

  1. 运行promise,等待解决.
  2. promise中取出结果并将其传递给func1。等待 func1 解决。
  3. func1中取出结果并将其传递给func2。等待 func2 解决。
  4. 等等

这里的主要区别在于,您 运行 按顺序排列每个 then 块,等待每个先前的承诺得到解决,然后再转到下一个。 (而在您没有等待 promise 解决之前)。

你的 promise 代码看起来像:

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 */)
}

解决此问题的第二种方法是使用 asyncawaitasyncawait 只是 promises 的语法糖。它在下面所做的是完全相同的,因此请确保您首先了解 promises 是如何工作的。这是带有 asyncawait 的代码:

// 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...")
}

onSubmit 中还有第二个类似的问题。这个想法是一样的;我会让你自己想办法(重要的是你必须 return 来自 signInPromise)。