ChatGPT解决这个技术问题 Extra ChatGPT

在 React 功能组件中使用 async/await

我刚刚开始将 React 用于一个项目,并且正在努力将 async/await 功能整合到我的一个组件中。

我有一个名为 fetchKey 的异步函数,它从我通过 AWS API Gateway 提供服务的 API 获取访问密钥:

const fetchKey = async authProps => {
  try {
    const headers = {
      Authorization: authProps.idToken // using Cognito authorizer
    };

    const response = await axios.post(
      "https://MY_ENDPOINT.execute-api.us-east-1.amazonaws.com/v1/",
      API_GATEWAY_POST_PAYLOAD_TEMPLATE,
      {
        headers: headers
      }
    );
      return response.data.access_token;

  } catch (e) {
    console.log(`Axios request failed! : ${e}`);
    return e;
  }
};

我正在使用 React 的 Material UI 主题,并希望使用它的 Dashboard 模板之一。不幸的是,仪表板模板使用了功能性无状态组件:

const Dashboard = props => {
  const classes = useStyles();

  const token = fetchKey(props.auth);
  console.log(token);

  return (
  ... rest of the functional component's code

https://i.stack.imgur.com/DVCD2.png

其次,如果我改用 token.then((data, error)=> console.log(data, error)),则两个变量都会得到 undefined。这似乎表明该函数尚未完成,因此尚未解析 dataerror 的任何值。然而,如果我尝试放置一个

const Dashboard = async props => {
  const classes = useStyles();

  const token = await fetchKey(props.auth);

React 强烈抱怨:

> react-dom.development.js:57 Uncaught Invariant Violation: Objects are
> not valid as a React child (found: [object Promise]). If you meant to
> render a collection of children, use an array instead.
>     in Dashboard (at App.js:89)
>     in Route (at App.js:86)
>     in Switch (at App.js:80)
>     in div (at App.js:78)
>     in Router (created by BrowserRouter)
>     in BrowserRouter (at App.js:77)
>     in div (at App.js:76)
>     in ThemeProvider (at App.js:75)

现在,我将是第一个声明我没有足够的经验来理解此错误消息发生了什么的人。如果这是一个传统的 React 类组件,我会使用 this.setState 方法设置一些状态,然后继续我的快乐方式。但是,我在此功能组件中没有该选项。

如何将 async/await 逻辑合并到我的功能性 React 组件中?

编辑:所以我只会说我是个白痴。返回的实际响应对象不是 response.data.access_token。是response.data.Item.access_token。嗬!这就是为什么结果被返回为未定义的原因,即使实际的承诺已经解决。

is it pending, or is it resolved 当您在控制台中检查它时它已解决 - 这并不意味着它在您记录它时已解决......控制台“撒谎”:p
token.then((data, error) ... .then 的回调函数(对于实际的 Promise/A+ 规范 Promise)将只有一个参数 - 其他“类似 Promise”的承诺(例如 jQuery)打破了这个规范 - 如果 dataundefined 表示 response.data.access_tokenundefined
你能检查一下你是否从 axios 调用中得到了正确的响应吗?如果 return response.data.access_token; 不是未定义的?

M
Milind Agrawal

你必须确保两件事

useEffect 类似于 componentDidMount 和 componentDidUpdate,所以如果你在这里使用 setState,那么你需要在用作 componentDidUpdate 时限制代码执行,如下所示:

function Dashboard() {
  const [token, setToken] = useState('');

  useEffect(() => {
    // You need to restrict it at some point
    // This is just dummy code and should be replaced by actual
    if (!token) {
        getToken();
    }
  }, []);

  const getToken = async () => {
    const headers = {
      Authorization: authProps.idToken // using Cognito authorizer
    };
    const response = await axios.post(
      "https://MY_ENDPOINT.execute-api.us-east-1.amazonaws.com/v1/",
      API_GATEWAY_POST_PAYLOAD_TEMPLATE,
      { headers }
    );
    const data = await response.json();
    setToken(data.access_token);
  };

  return (
    ... rest of the functional component's code
  );
}

getToken = async () => { 帮助我谢谢
需要注意的两个问题:1) useEffect 缺少 tokengetToken 作为依赖项。 2) 由于 getToken 是异步的,它可以在组件卸载后调用 setToken
1) 不,它不会丢失任何依赖项,因为它只需要调用一次,这只能通过 [] 来完成 2) 是的,这是可以通过检查屏幕是否可见来处理的有效场景。
如果它只被调用一次,那么为什么还要用 if (!token) 来限制它呢?如果 token 发生变化,它会再次触发副作用,这就是您要防范的原因,但这也意味着 token 确实是副作用的依赖项。
@MarcDingena 你是对的,如果我们没有添加依赖项,我们不需要 !token 。将 useEffect 用作 componentDidUpdate 时需要限制。
T
Tan Dat

使用 React Hooks,您现在可以在功能组件中实现与 Class 组件相同的功能。

import { useState, useEffect } from 'react';

const Dashboard = props => {
  const classes = useStyles();
  const [token, setToken] = useState(null);
  useEffect(() => {
     async function getToken() {
         const token = await fetchKey(props.auth);
         setToken(token);
     }
     getToken();
  }, [])


  return (
  ... rest of the functional component's code
  // Remember to handle the first render when token is null

也看看这个:Using Async await in react component


这应该是公认的答案,因为前一个要求 useEffect 是异步的。封装异步代码的最简单方法是在 async IIFE 中。
b
brunettdan

在解决 fetchKey 之前,组件可能会卸载或使用不同的 props.auth 重新呈现:

const Dashboard = props => {
  const classes = useStyles();

  const [token, setToken] = useState();
  const [error, setError] = useState();
  
  const unmountedRef = useRef(false);
  useEffect(()=>()=>(unmountedRef.current = true), []);

  useEffect(() => {
    const effectStale = false; // Don't forget ; on the line before self-invoking functions
    (async function() {
      const response = await fetchKey(props.auth);

      /* Component has been unmounted. Stop to avoid
         "Warning: Can't perform a React state update on an unmounted component." */
      if(unmountedRef.current) return;

        /* Component has re-rendered with different someId value
         Stop to avoid updating state with stale response */
      if(effectStale) return;

      if(response instanceof Error)
        setError(response)
      else
        setToken(response);
    })();
    return ()=>(effectStale = true);
  }, [props.auth]);

  if( error )
    return <>Error fetching token...{error.toString()}</>
  if( ! token )
    return <>Fetching token...</>

  return //... rest of the functional component's code

另一种方法是使用 SuspenseErrorBoundary

// render Dashboard with <DashboardSuspend>

const Dashboard = props => {
  const classes = useStyles();
  
  const [token, idToken] = props.tokenRef.current || [];

  // Fetch token on first render or when props.auth.idToken has changed
  if(token === void 0 || idToken !== props.auth.idToken){
    /* The thrown promise will be caught by <React.Suspense> which will render
       it's fallback until the promise is resolved, then it will attempt
       to render the Dashboard again */
    throw (async()=>{
      const initRef = props.tokenRef.current;
      const response = await fetchKey(props.auth);
      /* Stop if tokenRef has been updated by another <Dashboard> render,
         example with props.auth changed causing a re-render of 
         <DashboardSuspend> and the first request is slower than the second */
      if(initRef !== props.tokenRef.current) return;
      props.tokenRef.current = [response, props.auth.idToken];
    })()
  }

  if(props.tokenRef.current instanceof Error){
    /* await fetchKey() resolved to an Error, throwing it will be caught by 
       <ErrorBoundary> which will render it's fallback */ 
    throw props.tokenRef.current
  }

  return //... rest of the functional component's code
}

const DashboardSuspend = props => {

  /* The tokenRef.current will reset to void 0 each time this component is
     mounted/re-mounted. To keep the value move useRef higher up in the 
     hierarchy and pass it down with props or useContext. An alternative
     is using an external storage object such as Redux. */
  const tokenRef = useRef();

  const errorFallback = (error, handleRetry)=>{
    const onRetry = ()=>{
      // Clear tokenRef otherwise <Dashboard> will throw same error again
      tokenRef.current = void 0;
      handleRetry();
    }
    return <>
      Error fetching token...{error.toString()}
      <Button onClick={onRetry}>Retry</Button>
    </>
  }

  const suspenseFallback = <>Fetching token...</>

  return <ErrorBoundary fallback={errorFallback}>
    <React.Suspense fallback={suspenseFallback}>
      <Dashboard {...props} tokenRef={tokenRef} />
    </React.Suspense>
  </ErrorBoundary>
}

// Original ErrorBoundary class: https://reactjs.org/docs/error-boundaries.html
class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { error: null };
    }
    static getDerivedStateFromError(error) {
        // Update state so the next render will show the fallback UI.
        return { error };
    }
    componentDidCatch(error, errorInfo) {
        // You can also log the error to an error reporting service
        console.log(error, errorInfo);
    }
    render() {
        if (this.state.error) {
            // You can render any custom fallback UI
            const handleRetry = () => this.setState({ error: null });
            return typeof this.props.fallback === 'function' ? this.props.fallback(this.state.error, handleRetry) : this.props.fallback
        }
        return this.props.children;
    }
}

P
Praneeth Paruchuri
const token = fetchKey(props.auth);

这将返回一个承诺。要从中获取数据,这是一种方法:

let token = null;
fetchKey(props.auth).then(result => {
  console.log(result)
  token = result;
}).catch(e => {
  console.log(e)
})

让我知道这是否有效。

我重新创建了一个类似的示例:https://codesandbox.io/embed/quiet-wood-bbygk


不,这不起作用。正如我所说,then(result => ...) 中的 result 是未定义的。
那你一定是有什么问题。我重新创建了一个类似的例子,看看:codesandbox.io/embed/quiet-wood-bbygk
你是对的。我刚刚接受了你的回答。我没有足够仔细地检查我的响应对象。