【问题标题】:Function not being called when using async/await in React在 React 中使用 async/await 时未调用函数
【发布时间】:2020-08-13 08:54:34
【问题描述】:

我正在尝试在打开我的模式后进行异步获取调用。我需要那个调用,因为它获取了图像,并且获取响应大约需要 5 秒。

所以模式应该首先显示数据,然后一旦获取完成,它也应该显示获取数据。

我的问题是,在使用 () => {this.fetchImages(id) 调用函数时,它没有被调用。我认为这是因为该函数被分配而不是被调用。

但是当我在没有() => 的情况下调用函数fetchImages() 时,我得到了这个错误:

Invariant Violation: Minified React error #31

这是我的代码,为简单起见已删除无关部分:

renderModal = () => {
    ...
    return (

      <Modal open={openModal != null} onClose={this.closeModal}
             little showCloseIcon={true} styles={modalStyles}>
        <div className="item">
          ...
          {() => {this.fetchImages(id)
             .then(r => console.log("Fetch result" + r))}}
        </div>
      </Modal>
    );
  }

fetchImages = async (id) =>{
    console.log("Request started")
    try{
      let myImages = null;
      if(typeof(id) !== 'undefined' && id != null) {
        myImages = await fetchImages(id);
        console.log("Images: " + myImages);
        return (
          <div>
            {Array.isArray(myImages) && myImages.length !== 0 && myImages.map((item, key) =>
              <p>Image name: {item.name}, En device name: {item.name.en_US}</p>
            )}
          </div>
        );
      }
    } catch (e) {
      console.log("Failed")
    }
  }

编辑 通过按照jack.bensonGalAbra 的建议更改代码,我遇到了陷入死循环的问题。我将添加新代码:

一旦页面加载完毕,renderModal 就会在 render() 方法中调用:

{this.renderModal()}

然后我有一个显示模态的按钮,因为模态包含一行:

<Modal open={openModal != null} onClose={this.closeModal}
         little showCloseIcon={true} styles={modalStyles}>

从这里调用:

myMethod = (task) => {
    ...

    return (
  <div {...attrs}>
      ...
      <button onClick={() => {this.showModal(documents[0])}}>{translate('show_more')} &raquo;</button>
      </div>
      }
    </div>
  </div>
);};

以及显示模态的部分:

  showModal = (document) => {
    this.setState({ modalOpen: document });
  };

现在是新的renderModal()

renderModal = () => {
      const doThing = async () => {
      try {
        const newImages = await downloadDeviceImages(id);
        return { data: newImages };
      } catch (e) {
        console.log("Failed")
      }
    };

    const test = async (id) => {
      const imageData = await doThing(id);
      console.log("after", imageData.data);
      this.setState({
        imageData: imageData.data
      });
    };

    if(typeof(id) !== 'undefined' && id != null) {test(id);}

    return (

      <Modal open={openModal != null} onClose={this.closeModal}
             little showCloseIcon={true} styles={modalStyles}>
        <div className="item">
          ...
      <div>
        {this.state.imageData.map((item, key) =>
          <p>Device name: {item.name}, En device name: {item.name.en_US}</p>
        )}
      </div>
      </Modal>
    );
  }

这里的主要部分是该按钮将被多次单击,并且每次单击时可能具有不同的 ID,因此每次都应发出新请求。

【问题讨论】:

    标签: reactjs react-native asynchronous async-await fetch


    【解决方案1】:

    目前您的代码包含一个与 HTML 元素混合的异步函数。

    取而代之的是,您拥有 React 的状态,该状态将在获取数据后负责重新渲染:

    const ModalWrapper = ({ id }) => {
        const [myImages, setMyImages] = React.useState([]);
    
        const fetchImages = async (id) => {
          try {
            const newImages = await fetchImagesCall(id);
            setMyImages(newImages); // Will trigger a re-render of the component, with the new images
          } catch (e) {
            console.log("Failed")
          }
        }
    
        React.useEffect(() => {
            fetchImages(id);
        }, []); // This empty array makes sure the `fetchImages` call will occur only once the component is mounted
    
        return (
          <Modal>
            <div className="item">
              <div>
                {myImages.map((item, key) =>
                  <p>Image name: {item.name}, En device name: {item.name.en_US}</p>
                )}
              </div>
            </div>
          </Modal>
        );
    }
    

    【讨论】:

    • 目前代码的工作方式如下: Modal 是第一次加载但没有显示,因为它没有数据。当单击特定“项目”的按钮时,会显示Modal,它应该使用该特定 ID 发出请求。然后Modal 可以关闭并使用不同的 ID 重新打开。这在这种情况下也有效吗?
    • 您需要添加模态框的打开/关闭状态并处理应作为道具传输的id。除此之外,ModalWrapper 应该专门处理数据获取和呈现
    • 没有Hooks,有没有办法做同样的事情?因为我用的是React16.2
    • 如果您使用基于类的组件,您将需要使用诸如componentDidMount 和类状态之类的生命周期方法。这是一个基于我回答中的代码的示例:codesandbox.io/s/clever-volhard-bxewi?file=/src/App.js
    • @kataroty 你需要使用一个类组件,在componentDidMount 中声明和调用fetchImages
    【解决方案2】:

    所以,this.fetchImages(id) 返回一个 Promise,React 不知道如何渲染。这就是你得到错误的原因。当您将它包装在() =&gt; { ... } 中时,您实际上是在创建一个包装您的承诺的函数,但您永远不会调用该函数。这就是它不被调用的原因。就像在说:

    function aThingToDo() {
      console.log('Did thing!')
    }
    
    function doThing() {
      aThingToDo();
    }
    

    请注意,我声明了函数,但它们都没有被调用。您需要通过在函数后面显式添加括号来调用该函数(如doThing())。

    这解释了您的错误以及未调用您的函数的事实。现在,如何处理它。您想将您的获取包装在useEffect 中(因为应该在render 期间完成async 的内容)。然后,如果您想要render 期间的数据,您可以在它完成后设置状态,这将触发重新渲染。我将我正在谈论的内容放在一起quick example

    import React, { useEffect, useState } from "react";
    import "./styles.css";
    
    const doThing = async () => {
      console.log("doing a thing");
      return { data: "My data" };
    };
    
    export default function App() {
      const [data, setData] = useState("");
    
      useEffect(() => {
        const test = async () => {
          const data = await doThing();
          setData(data);
        };
        test();
      }, []);
    
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
          {data && data.data}
        </div>
      );
    }
    

    这是一个使用类组件的similar example

    import React from "react";
    import "./styles.css";
    
    const doThing = async () => {
      console.log("doing a thing");
      return { message: "New message" };
    };
    
    export default class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          data: "Default message"
        };
      }
    
      componentDidMount() {
        const test = async () => {
          const data = await doThing();
          console.log("after", data.message);
          this.setState({
            data: data.message
          });
        };
        test();
      }
    
      render() {
        console.log("rendering", this.state.data);
        return (
          <div className="App">
            <h1>Hello CodeSandbox</h1>
            <h2>Start editing to see some magic happen!</h2>
            {this.state.data}
          </div>
        );
      }
    }
    

    希望有帮助!

    【讨论】:

    • 感谢您添加带有类的版本。对我来说,它现在进入了一个无限循环,永远打印出“After”。试图找出原因。也许你碰巧知道为什么?
    • 我不确定你的意思。我刚刚检查了这个例子,它并没有无限循环。您是在谈论您正在处理的代码吗?您是否在render 方法中设置状态?这将导致重新渲染,这将导致无限循环。我需要更多信息来进一步提供帮助。
    • 我编辑了这个问题,但似乎是的,我正在渲染方法中设置状态。将尝试在方法之外进行。也非常感谢您的宝贵时间。
    • 通常有两个地方经常发生设置状态:在非渲染生命周期方法中(通常是componentDidMount)或在事件处理程序中(如onClick)。如果您需要将状态设置为组件首次加载时发生的事件或查询的结果,请使用componentDidMount。否则,如果您需要对用户事件做出反应,请将您的状态设置代码放入事件处理程序中。祝你好运!
    • 没有看到你的确切代码很难说。但是,根据您问题中的 sn-ps,我拼凑了一个我认为应该适合您的示例:codesandbox.io/s/sweet-grass-erqcx?file=/src/App.js
    猜你喜欢
    • 2023-03-04
    • 2017-12-17
    • 2019-02-17
    • 2019-01-05
    • 2016-07-31
    • 2019-09-19
    相关资源
    最近更新 更多