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

Posted

技术标签:

【中文标题】如何使 Javascript/React/Typescript 获取调用异步?【英文标题】:How to make a Javascript/React/Typescript fetch call asynchronous? 【发布时间】:2020-03-25 08:14:27 【问题描述】:

考虑以下 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 但这不起作用,所以我不知道从哪里开始。

【问题讨论】:

【参考方案1】:

您可能想进一步了解 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:

    运行promise,并等待它解决。 从promise 获取结果并将其传递给func1。等待func1 解决。 从func1 获取结果并将其传递给func2。等待func2 解决。 等

这里的关键区别在于您按顺序运行每个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 只是对 Promise 的语法糖。它在下面的作用是完全相同的,因此请确保您首先了解 Promise 是如何工作的。这是带有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 中还有第二个类似的问题。想法是一样的;我会让你自己弄清楚(重要的是你必须从signIn返回一个Promise)。

【讨论】:

我怎样才能做到这一点,这样我就不必从signIn 传回承诺,而是一种signIn 有效载荷?我可以使用另一个Promise 将我正在做的事情包装在signIn 中并使用键入的resolve/reject 吗?如果我的要求没有意义,请告诉我。 如果你想从signIn返回一些有效载荷,只需将有效载荷包装在一个承诺中。例如,您可以在signIn 的末尾执行类似return fetch(/* ... */).then(/* ... */).then(/* ... */).then(() =&gt; ( some: 'payload' )) 的操作。那么payload就是解析signIn的返回值后得到的。【参考方案2】:

为了使用异步等待,您需要从调用中返回一个承诺。所以基本上你不执行 .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 
    
  

【讨论】:

【参考方案3】:

如果您希望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...");
  

【讨论】:

【参考方案4】:

只需添加另一个 .then

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

【讨论】:

以上是关于如何使 Javascript/React/Typescript 获取调用异步?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 RelativeLayout 半透明但不使活动

如何用word使图片上下居中

如何使图像自动调整大小,使宽度为 100% 并相应调整高度?

如何使 UISegmentedcontrol 透明?

如何使 textarea 填充 div 块?

如何使 UITableViewCell 显示为禁用?