【问题标题】:Trouble fetching data with Async/Await使用 Async/Await 获取数据时遇到问题
【发布时间】:2020-12-11 10:28:29
【问题描述】:

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

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?

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

【问题讨论】:

标签: reactjs async-await fetch es6-promise asynchronous-javascript


【解决方案1】:

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

改为使用普通的for 循环:

for (let alphaCode of fetchedData.borders) {

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

您也可以考虑使用Promise.all,如果可以接受无需等待前一个解决就可以创建promise。然后你可以等待Promise.all 的结果。在您尝试这样做时,您没有将任何内容传递给Promise.all,因为forEach 总是返回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 所需要的。等待发生在Promise.all 之前的await。

【讨论】:

  • 能否详细说明“那些回调是同步调用的”?
  • 当一个回调被调用,它遇到一个await,它在那个时刻返回,返回一个promise。之后立即调用所有其他回调,它们也会发生同样的情况。代码继续执行forEach 循环之后的语句。只有当等待的 Promise 开始解决时,执行才会回到那些回调继续执行,但是 forEach 早就完成了。
【解决方案2】:

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', ... ]

【讨论】:

  • 所以据我了解-.map() 获取每个borders 成员并运行fetchNeighbor,它返回相应的.name。可能我还是误解了Promise.all 的工作原理,但是它收集了每个promise 的返回值(本例中为字符串)并将它们放入一个数组中。我说的对吗?
  • 是的。 Array.map 返回一个新数组,其中包含对原始数组中的每个元素运行函数的结果。因此,在这种情况下,它会从您的边框数组中生成一组 fetch 承诺。当所有这些承诺都解决时,Promise.all 会以一系列结果解决。我链接了then()s 以获取 json 并仅返回名称,这似乎是您所追求的。
  • 哦,你的意思是,promise 的返回值的收集任务是由.map() 执行的,Promise.all 只是坐在那里等待要解决的 promises,然后将数组 returned .map().
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 2018-12-05
    • 2018-03-24
    • 2018-08-21
    • 2021-03-03
    • 2020-03-25
    • 1970-01-01
    • 2022-01-24
    • 2013-09-27
    • 1970-01-01
    相关资源
    最近更新 更多