Promise 仅在页面刷新时正确解析

Promise only resolves correctly on page refresh

我正在玩弄一个 API,它获取 Pokemon 列表和相应的数据,如下所示。

export function SomePage() {
const [arr, setArray] = useState([]);

   useEffect(() => {
    fetchSomePokemon();
  }, []);


  function fetchSomePokemon() {
    fetch('https://pokeapi.co/api/v2/pokemon?limit=5')
     .then(response => response.json())
     .then((pokemonList) => {
       const someArray = [];
     pokemonList.results.map(async (pokemon: { url: string; }) => {
       someArray.push(await fetchData(pokemon))
     })
     setArray([...arr, someArray]);
    })
   }

   async function fetchData(pokemon: { url: string; }) {
    let url = pokemon.url
     return await fetch(url).then(async res => await res.json())
    }

    console.log(arr);

  return (
      <div>
      {arr[0]?.map((pokemon, index) => (
          <div
            key={index}
          >
            {pokemon.name}
          </div>
        ))
      }
      </div>
  );
}

代码有效(有点),但是在第一次渲染时,即使 console.log 输出数据,地图也不会显示任何内容。只有刷新页面后,才会显示正确的数据。我觉得这与没有正确处理承诺有关。也许有人可以帮助我。 TIA

预期输出:初始渲染时填充的数据(在这种情况下,将显示 pokemon 名称)

useEffect(() => { fetchSomePokemon(); }, []);

[] 告诉 React 这个效果的发生没有依赖关系,

在此处阅读更多内容 https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

在您的第一次渲染中,您还没有数据,因此 arr[0] 不存在,您无法在其上 .map,因此它崩溃了。映射前需要检查数据是否已经存在

使用 optional chaining,如果没有数据,它不会在您的第一次渲染时抛出错误,并且当数据到达时它会正确渲染并且 re-renders。

...
  return (
    <div>
      {arr[0]?.map((pokemon, index) => (
        <div key={index}>{pokemon.name}</div>
      ))}
    </div>
  );
}

in-build map 数组上的方法本质上是同步的。在 fetchSomePokemon 中,您需要 return 来自地图回调函数的承诺,因为您正在其中编写异步代码。

现在 pokemonList.results.map 编辑的数组 return 中的项目是承诺。您需要在 pokemonList.results.mapawait 上使用 Promise.all

await Promise.all(pokemonList.results.map(async (pokemon: { url: string; }) => {
       return fetchData.then(someArray.push(pokemon))
     }));

解决问题的一种方法是 awaituseEffect() 中获取数据。

这是一个 POC:

export function Page() {
  const [pokemon, setPokemon] = useState([]);

  // will fetch the pokemon on the first render
  useEffect(() => {
    async function fetchPokemon() {
      // ... logic that fetches the pokemon
    }
    
    fetchPokemon();
  }, []);

  if (!pokemon.length) {
    // you can return a spinner here
    return null;
  }
  
  return (
    <div>
      {pokemon.map(item => {
        // return an element
      })}
    </div>
  );
 }