【问题标题】:Assign Promise To Variable In React在 React 中将 Promise 分配给变量
【发布时间】:2020-12-07 21:57:18
【问题描述】:

总的来说,我对 JavaScript/React 非常陌生,并且在 Promise 和 async 的概念上苦苦挣扎。

首先我有getSimById,这是一个JS文件中的API调用,它返回一个Promise:

export function getSimById(simId) {
  return fetch(simsUrl + "/results/" + simId, {
    method: "GET",
    headers: new Headers({
      Authorization: "Basic " + base64.encode(login + ":" + password)
    })
  })
    .then(handleResponse)
    .catch(handleError);
}

而handleResponse 是一个异步函数。

export async function handleResponse(response) {
  if (response.ok) {
    let someResponse = response.json();
    return someResponse;
  }

  if (response.status === 400) {
    throw new Error(error);
  }

  const error = await response.text();
  throw new Error("Network response was not ok.");
}

现在我有一个返回 Table 的功能组件:

import React, { useState, useEffect } from "react";
import { getSimById } from "../api/outrightSimulatorApi";

function SimulationReport(props) {

  const location = useLocation();
  const [simResult, setSimResult] = useState([]);

  useEffect(() => {
    getSimById(location.state.simId).then(result => setSimResult(result));
  }, []);

  let reformattedData = getSimById(location.state.simId).then(
    data => reformattedData = data?.markets?.length ? data.markets.reduce(
      (accumulator, market) =>
        market.selections.map(({ name, probability }, index) => ({
          ...accumulator[index],
          "Team name": name,
          [market.name]: probability,
        })),
      [],
    ) : null);

  return (
      <div>
          <Table striped bordered hover size="sm" responsive>
            <thead>
              <tr>{

              }
              </tr>
            </thead>
            <tbody>{

             }
            </tbody>
          </Table>
      </div>
  );

在这段代码中,我想将reformattedData 映射为一个数组,并最终将其值映射到返回的Table 中。但是,reformattedData 在此实例中不是数组,实际上是Promise。因此,每当我尝试访问reformattedData[0] 之类的内容时,它实际上会返回undefined,并且我无法映射Table 中的值。在这种情况下,如何将 Promise 分配给变量,以便对其执行操作?

【问题讨论】:

  • 您需要映射simResult 数组,而不是为getSimById 调用reformattedData。通过在then 之后添加catch,确保在useEffect 中正确处理错误。
  • 你为什么在两个不同的地方打电话给getSimById?您应该只在 useEffect 回调中执行此操作,而不是在组件函数中内联。

标签: javascript json reactjs promise optional-chaining


【解决方案1】:

你不应该在两个不同的地方调用getSimById,它应该只在useEffect回调中,它应该将location.state.simId列为依赖项。

类似的东西:

function SimulationReport(props) {

  const location = useLocation();
  const [simResult, setSimResult] = useState([]);

  useEffect(() => {
    getSimById(location.state.simId).then(data => {
        const reformattedData = data?.markets?.length ? data.markets.reduce(
          (accumulator, market) =>
            market.selections.map(({ name, probability }, index) => ({
              ...accumulator[index],
              "Team name": name,
              [market.name]: probability,
            })),
          [],
        ) : null;
        setSimResult(reformattedData); // *** Set state here
      })
      .catch(error => {
        // *** Handle/report error
      });
  }, [location.state.simId]); // *** Note the dependency

  return (
      <div>
          <Table striped bordered hover size="sm" responsive>
            <thead>
              <tr>{

              }
              </tr>
            </thead>
            <tbody>{
              // *** Use `simResult` when rendering
              simResult.map(entry => <markup>for entry</markup)
             }
            </tbody>
          </Table>
      </div>
  );
}

还有一个问题:如果你的效果在它们到达之前再次运行,你想忽略异步获得的结果。为此,您从 useEffect 回调中返回一个函数,以便 React 可以告诉您何时发生,如下所示:

  useEffect(() => {
    let cancelled = false; // ***
    getSimById(location.state.simId).then(data => {
        if (cancelled) {
            // Don't use it
            return;
        }
        const reformattedData = data?.markets?.length ? data.markets.reduce(
          (accumulator, market) =>
            market.selections.map(({ name, probability }, index) => ({
              ...accumulator[index],
              "Team name": name,
              [market.name]: probability,
            })),
          [],
        ) : null;
        setSimResult(reformattedData);
      })
      .catch(error => {
        // Handle/report error
      });
      return () => {        // *** A callback React will use when the effect runs again
        cancelled = true;   // *** Remember that *this* call has been cancelled
      };
  }, [location.state.simId]);

Dan Abramov 的This article 提供了一些关于钩子的一般信息,尤其是useEffect。

【讨论】:

  • 抱歉,} 或其中的某些内容放错了位置,漏掉了 .catch。如果您没有看到.catch,请点击刷新。
  • 你能解释一下cancelled = true在组件卸载后做了什么吗?在我看来是没用的,因为组件将被卸载,除非你从 DOM 或类似的东西中删除事件,否则是错误的,并且可能会导致另一个混乱。
  • 感谢您的所有解释,但我在同一点上,刚刚更新了您的代码和框codesandbox.io/s/…。 PD只是想弄清楚我是否错过了什么,谢谢。
  • 感谢您的 cmets T.J.克劳德,非常感谢。
  • 现在我知道当事情不可取消时该怎么做:-),非常感谢
【解决方案2】:

好的,你的 api 调用按预期工作,你收到了

useEffect(() => {
    getSimById(location.state.simId).then(result => setSimResult(result));
  }, []);

可以像这样简化同时解析数据

getSimById(location.state.simId).then(parseSimResult);

但你的问题在于let 这里。

可能的解决方案是:

组件外(可能是 utils)?

export const parseSimResults = (simResults) => {
  return simResults.markets.reduce(
      (accumulator, market) =>
        market.selections.map(({ name, probability }, index) => ({
          ...accumulator[index],
          "Team name": name,
          [market.name]: probability,
        })),
      [],
    )
}; 

然后在渲染图中将simResults 扔到你的组件渲染中

<thead>
  {simResult && simResults.map(r => {
    <tr key="someKEY">
         {
          ...
         }
    </tr>
  })}
</thead>

生成的完整代码

const parseSimResults = (simResults) => {
  return simResults.markets.reduce(
      (accumulator, market) =>
        market.selections.map(({ name, probability }, index) => ({
          ...accumulator[index],
          "Team name": name,
          [market.name]: probability,
        })),
      [],
    )
}; 

const MyComponent.... {
  const [simResults, setSimResults] = useState([]);

  useEffect(() => {
    getSimById(location.state.simId).then(parseSimResults);
  }, []);

  return simResults.map(r => <your JSX>)
}

【讨论】:

    【解决方案3】:

    在您的useEffect 中,您已经在调用getSimById() 并存储了结果,因此无需立即再次调用它。

    请尝试迭代 simResult 数组。这应该具有您想要引用的值。

    【讨论】:

    • 感谢 Kevin,但这不会返回一个 Promise 吗?
    • @clattenburgcake - 不,没有给出你在哪里使用setSimResult。但我认为您可能确实想要您编写的重新格式化逻辑;你只需要重新定位它。
    • @clattenburgcake 不应该,但澄清此代码的另一种方法是坚持使用 async/await 语法或 .then 语法。它们通常是可互换的,我相信这会容易得多跟踪 Promise 的来源。
    猜你喜欢
    • 1970-01-01
    • 2018-03-13
    • 2017-01-16
    • 2021-06-19
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2014-05-11
    • 2012-01-06
    相关资源
    最近更新 更多