将异步函数中的数据传递给另一个函数 React JS

3

我有一个异步函数的问题,我需要在另一个函数中使用track.user,但我的getTracks()函数是异步的。我不知道如何获取它。

const Player = ({trackUrl, index, cover, id}) => {
    const [track, setTrack] = useState({})
    const [user, setUser] = useState({})

    useEffect(() => {
        const getTracks = async () => {
            await httpClient.get(`/track/${id}`)
                .then((response) => {
                    setTrack(response.data);
                })
        }
        getTracks();
        getUser() // track.user undefined 
    }, [])

    const getUser = async() => {
        await httpClient.get(`/profile/${track.user}/`)
            .then((response) => {
                setUser(response.data);
            })
    }
}

这里涉及到异步代码和如何使用效果的问题。你打算如何运行 getUser 函数?你只想在组件挂载时运行它吗?还是它将成为某个事件处理程序函数,比如当他们点击一个按钮时? - Jayce444
我想在组件挂载时使用 getUser。 - Volear
3个回答

2
你可以将第二个请求移动到依赖于第一个请求的getTracksthen块中。此外,不应混合使用thenawait
useEffect(() => {
    const getTracks = () => {
        httpClient.get(`/track/${id}`)
            .then((response) => {
                setTrack(response.data);
                httpClient.get(`/profile/${response.data.user}/`)
               .then((response) => {
                 setUser(response.data);
               })
            })
    }
    getTracks();
}, [])

我遇到了一个错误,意外的保留字 'await'。 - Volear
因为你不能同时使用 then 和 async-await。 - Priyen Mehta
1
@Volear,你不应该在await内部使用then。我已经更新了我的答案。 - NeERAJ TK
1
因为在这段代码中,async 也需要在 then 回调函数中使用。但实际上,你不应该混合使用 thenable 和 async/await 代码。@Volear - Andy
你修改了这段代码,现在使用async有什么意义?@NeERAJTK - Andy
@Andy 对不起!我已经将它删除了。 - NeERAJ TK

2

我建议在组件的开头声明这两个函数(你可以稍后通过useCallback来优化它们,但在这个阶段并不是很重要)。

const getTracks = async () => {
  await httpClient.get(`/track/${id}`)
    .then((response) => {
      setTrack(response.data);
    })
  }

const getUser = async() => {
  await httpClient.get(`/profile/${track.user}/`)
    .then((response) => {
      setUser(response.data);
  })
}

我会在 useEffect 钩子中调用一个 async 函数。有几种方法可以实现:你可以在 useEffect 钩子中声明一个 async 函数并立即调用它,或者你可以调用一个匿名的 async 函数。出于简洁性的考虑,我更喜欢后者,代码如下:

useEffect(() => {
  (async () => {
    await getTracks();
    getUser();
  })();
}, []);

现在当您调用getUser时,您应该确保getTracks已经设置了track变量。

以下是完整的组件:

const Player = ({trackUrl, index, cover, id}) => {
  const [track, setTrack] = useState({})
  const [user, setUser] = useState({})

  const getTracks = async () => {
    await httpClient.get(`/track/${id}`)
      .then((response) => {
        setTrack(response.data);
      })
    }

  const getUser = async() => {
    await httpClient.get(`/profile/${track.user}/`)
      .then((response) => {
        setUser(response.data);
      })
    }

  useEffect(() => {
    (async () => {
      await getTracks();
      getUser();
    })();
  }, []);
}

EDIT 07/18/22

根据Noel的评论和链接的代码沙盒,我发现我的答案不起作用的原因是track变量在执行getTrack()钩子后不可用:它将在随后的渲染中可用。

我的解决方案是添加第二个useEffect钩子,每次track变量发生更改时执行。我创建了两个使用jsonplaceholder端点的解决方案,一个(见此处)保留了大部分原始解决方案但增加了复杂性,另一个(此处)通过将两个方法从setTracksetUser钩子中解耦,大大简化了代码。

这里我会贴出更简单的一个版本,符合OP的要求。

export default function Player({ trackUrl, index, cover, id }) {
  const [track, setTrack] = useState({});
  const [user, setUser] = useState({});

  const getTracks = async () => {
    // only return the value of the call
    return await httpClient.get(`/track/${id}`);
  };

  const getUser = async (track) => {
    // take track as a parameter and call the endpoint
    console.log(track, track.id, 'test');
    return await httpClient.get(`profile/${track.user}`);
  };

  useEffect(() => {
    (async () => {
      const trackResult = await getTracks();
      // we call setTrack outside of `getTracks`
      setTrack(trackResult);
    })();
  }, []);

  useEffect(() => {
    (async () => {
      if (track && Object.entries(track).length > 0) {
        // we only call `getUser` if we are sure that track has at least one entry
        const userResult = await getUser(track);
        console.log(userResult);
        setUser(userResult);
      }
    })();
  }, [track]);
  return (
    <div className="App">{user && user.id ? user.id : "Not computed"}</div>
  );
}

现在当您调用getUser时,应确保getTracks已经设置了track变量。您在代码中没有做到这一点。当前的代码使用空对象调用getUser。 - Noel
1
不好意思,您对 await 关键字的理解有误。await 并不会等待 setUser 部分完成。请查看 此处 的沙盒控制台以获取演示。 - Noel
是的,你说得对,我的代码确实有误,但原因并不是await不等待setUser部分。即使在setUser部分执行之前,用户也无法使用:它将在下一个渲染中可用。解决方法是在单独的useEffect钩子中使用“user”变量(如答案中已经提出的)。这里是我的沙盒。感谢您的提醒,我会编辑我的回答。 - Giorgio Tempesta
@Noel 我不确定我完全理解你的意思,但在你的第一条评论中,我认为你是在说当我调用 getUser() 时,getTracks() 没有被执行。看一下这个新的沙盒 链接,里面有 console.logs:在 await getTracks(); 后的日志会在 getTracks 结束后打印,所以 await 真的等待函数的完整执行。如果我没有理解你的意思,请告诉我。 - Giorgio Tempesta
2
我写信是关于你的第一条评论:“在 getTracks 前使用 await 关键字可以确保 getUser 只会被调用一次,而且 getUser 中的所有内容(包括 setUser 部分)都已经执行完毕。”最后一行不是真的。 - Noel
显示剩余3条评论

1

不应该在async/await中混合使用then。你应该使用另一个useEffect,以观察track状态的变化并调用getUser获取新数据。

function Player(props) {

  const { trackUrl, index, cover, id } = props;

  const [ track, setTrack ] = useState({});
  const [ user, setUser ] = useState({});

  async function getTracks(endpoint) {
    const response = await httpClient.get(endpoint);
    const data = await response.json();
    setTrack(data);
  }

  async function getUser(endpoint) {
    const response = await httpClient.get(endpoint);
    const data = await response.json();
    setUser(data);
  }

  useEffect(() => {
    if (id) getTracks(`/track/${id}`);
  }, []);

  useEffect(() => {
    if (track.user) getUser(`/profile/${track.user}`);
  }, [track]);

}


如果在第二个 useEffect 中使用 if(track),由于状态声明在对象中,它将始终返回 true,从而破坏应用程序。 - NeERAJ TK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接