使用 Async/Await 获取数据时遇到问题

Trouble fetching data with Async/Await

您好,我正在尝试获取一个国家/地区的数据,然后我想获取其邻国的名称。

import React from 'react';

export default function DetailsRoute({ match }) {

    const [countryData, setCountryData] = React.useState({});
    const [borderCountries, setBorderCountries] = React.useState([]);

    React.useEffect(() => {
        fetchCountryData();
    }, [])

    const fetchCountryData = async () => {
        /* fetch country by Name */
        const response = await fetch(`https://restcountries.eu/rest/v2/name/${match.params.country}`);
        const fetchedData = (await response.json())[0];

        setCountryData(fetchedData);

        const neighbors = [];

        /* Extract 'alphaCode' for each bordered country and fetch its real name */

        fetchedData.borders.forEach(async (alphaCode) =>  {

            const response = await fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`);
            const fetchedNeighbor = await response.json();

            neighbors.push(fetchedNeighbor.name);
        });

        /* THIS DOESN'T WAIT FOR NEIGHBORS TO BE FILLED UP */
        setBorderCountries(neighbors);
    }

    
    return (
        <article>
            <h1>{countryData.name}</h1>
            {borderCountries.map(countryName => <h2>{countryName}</h2>)}
        </article>
    )
}

如您所见,setBorderCountries(neighbors) 不会 运行 异步。但我不知道如何让它等待 forEach() 循环完成。

在 Whosebug 的某个地方,我看到 Promise.all() 并尝试实现它,但我真的不知道它在语法上是否正确-

Promise.all(
    fetchedData.borders.forEach(async (alphaCode) => {

    const response = await fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`);
    const fetchedNeighbor = await response.json();

    neighbors.push(fetchedNeighbor.name);
    })
)
.then(() =>
    setBorderCountries(neighbors)
)

我的问题是如何让 setBorderCountries(neighbors) 等到 forEach() 循环完成填满 neighbors

也许对我的代码有一些优化建议?

forEach 循环立即完成,因为等待发生在回调中(仅),但这些回调都是同步调用的,因此 forEach 在任何等待的承诺解决之前完成.

而是使用普通的 for 循环:

for (let alphaCode of fetchedData.borders) {

现在,该循环内的 await 是顶层 async 函数的一部分,因此它按您的预期工作。

如果可以接受创建承诺而不等待前一个承诺解决,您也可以考虑使用 Promise.all。然后就可以等待Promise.all的结果了。在您尝试这样做时,您没有将任何内容传递给 Promise.all,因为 forEach 总是 returns undefined。正确的方法是使用 .map 如下:

const neighbors = await Promise.all(
    fetchedData.borders.map(async (alphaCode) => {
        const response = await fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`);
        const fetchedNeighbor = await response.json();
        return fetchedNeighbor.name; // return it!!
    });
)
setBorderCountries(neighbors);

请注意,这里 .map 迭代也是同步完成的,但它是 returns 一组承诺,这正是 Promise.all 所需要的。等待发生在 Promise.all.

之前的 await

Promise.all 采用一系列承诺。您的代码将 forEach 调用(未定义)的结果传递给 Promise.all,这不会执行您想要的操作。

如果您改用 map,则可以创建一组请求承诺。大致如下:

const fetchNeighbor = async (alphaCode) => {
  return fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`)
    .then(response => response.json())
    .then(n => n.name);
}

const neighborNames = await Promise.all(fetchedData.borders.map(fetchNeighbor));
setBorderCountries(neighbors);

Array.map 通过 运行 源数组中每个元素的给定函数生成一个新数组。所以这两个大致等价:

const promises = fetchedData.borders.map(fetchNeighbor);
const promises = [];
fetchedData.borders.forEach(alphaCode => {
  promises.push(fetchNeighbor(alphaCode));
});

你现在有一组承诺(因为这就是 fetchNeighbor returns)你可以传递给 Promise.all:

const results = await Promise.all(promises);

Promise.all 使用一组已解析的承诺值进行解析。由于 fetchNeighbor 最终解析为名称,因此您现在有一个名称数组。

const results = await Promise.all(promises);

console.log(results);
// ['Country A', 'Country B', 'Country C', ... ]

我认为你应该使用这样的东西。

const start = async () => {
  await asyncForEach([1, 2, 3], async (num) => {
    await waitFor(50);
    console.log(num);
  });
  console.log('Done');
}
start();

这篇文章我认为是一个很好的学习资源:async/await with for each