1

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

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()循环完成。

在stackoverflow的某个地方,我看到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

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

4

3 回答 3

2

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

而是使用普通for循环:

for (let alphaCode of fetchedData.borders) {

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

如果可以接受,您也可以考虑使用Promise.all在不等待前一个解决的情况下创建 Promise。然后你就可以等待Promise.all. 在您尝试这样做时,您没有Promise.allforEach往常一样将任何东西传递给返回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迭代也是同步完成的,但它返回一个 promise 数组,这正是Promise.all需要的。等待发生在awaitthat 之前Promise.all

于 2020-08-22T05:54:09.077 回答
1

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));
});

你现在有一个可以传递给 Promise.all 的 Promise 数组(因为这是 fetchNeighbor 返回的):

const results = await Promise.all(promises);

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

const results = await Promise.all(promises);

console.log(results);
// ['Country A', 'Country B', 'Country C', ... ]
于 2020-08-22T05:57:16.927 回答
0

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

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

于 2020-08-22T06:42:17.953 回答