0

我在向对象添加键时遇到了一些麻烦,如下所示:

    const recursiveFetchAndWait = useCallback(
        (url) => {
            setLoading(true);
    
            fetch(url)
                .then(async response => {
                    if (response.status === 200) { // Checking for response code 200
                        const xml = await response.text();
                        setLoading(false);
                        return XML2JS.parseString(xml, (err, result) => { // xml2js: converts XML to JSON
                            if (result.items.$.totalitems !== '0') { // Only processing further if there are returned results
                                result.items.item.forEach(game => {
                                    /* Fetching the statistics from a separate API, because the base API doesn't include these */
                                    const gameId = game.$.objectid;
                                    fetch('https://cors-anywhere.herokuapp.com/https://www.boardgamegeek.com/xmlapi2/thing?id=' + gameId + '&stats=1')
                                        .then(async response => {
                                            const xml = await response.text();
                                            return XML2JS.parseString(xml, (err, result) => {
                                                console.log('result', result); // This returns data.
                                                game.statistics = result.items.item[0].statistics[0].ratings[0];
                                                // setStatistics(...{statistics}, ...{gameId: result.items.item[0].statistics[0].ratings[0]})
                                            })
                                        })
    
                                    console.log('game', game); // This returns the object with the newly statistics key.
                                    console.log('STATS!', game.statistics); // This doesn't recognize the statistics key?!
    
                                    /* Going through the array and changing default values and converting string numbers to actual numbers */
                                    if (game.stats[0].rating[0].ranks[0].rank[0].$.value === 'Not Ranked')
                                        game.stats[0].rating[0].ranks[0].rank[0].$.value = 'N/A';
                                    else {
                                        game.stats[0].rating[0].ranks[0].rank[0].$.value = Number(game.stats[0].rating[0].ranks[0].rank[0].$.value);
                                    }
    
                                    game.stats[0].$.minplayers = Number(game.stats[0].$.minplayers);
                                    if (isNaN(game.stats[0].$.minplayers))
                                        game.stats[0].$.minplayers = '--';
    
                                    game.stats[0].$.maxplayers = Number(game.stats[0].$.maxplayers);
                                    if (isNaN(game.stats[0].$.maxplayers))
                                        game.stats[0].$.maxplayers = '--';
    
                                    game.stats[0].$.maxplaytime = Number(game.stats[0].$.maxplaytime);
                                    if (isNaN(game.stats[0].$.maxplaytime))
                                        game.stats[0].$.maxplaytime = '--';
    
                                    if (game.yearpublished === undefined)
                                        game.yearpublished = ['--'];
                                });
                                setGameList(result.items.item)
                            }
                        });
                    } else if (response.status === 202) { // If the status response was 202 (API still retrieving data), call the fetch again after a set timeout
                        setTimeoutAsCallback(() => recursiveFetchAndWait(url));
                    } else
                        console.log(response.status);
                })
        },
        [],
    );

以下是 console.logs 的结果

我担心这个问题与异步调用有关,但我对为什么第一个console.log()工作正常感到困惑。如果是异步问题,我该如何解决?

4

3 回答 3

0

立即移动console.log('STATS!', game.statistics);到下方game.statistics =

或者,在函数内执行所有操作async

(async () => {
  for (const game of games) {
    const response = fetch('https://www.boardgamegeek.com/xmlapi2/thing?id=' + game.$.objectid + '&stats=1');
    const xml = await response.text();
    await new Promise(resolve => {
      XML2JS.parseString(xml, (err, result) => {
        game.statistics = result.items.item[0].statistics[0].ratings[0];
        resolve();
      });
    });
  }


  games.forEach(game => {
    console.log('game', game);
    console.log('STATS!', game.statistics); 
  });
})();
于 2020-07-26T23:12:33.287 回答
0

您的第一个 console.log 有效,因为“游戏”变量已经存在并且在您发出异步获取请求之前就包含数据。您可以在 fetch 之前调用它,它仍然可以。

你的第二个 console.log 试图输出“game.statistics”,在 fetch 返回任何数据之前运行。这是因为异步调用不会停止并等待代码完成异步任务,然后再继续执行下一行代码。这就是异步代码块的预期目的。一旦返回执行任何依赖于返回数据的操作,它将在回调中运行代码和响应。但不会阻止浏览器继续通过代码运行其余代码行。

为了实现您似乎正在尝试做的事情,您可以将获取数据后需要运行的任务放在单独的函数中,然后使用响应调用它。

games.forEach(game => {
    fetch('https://www.boardgamegeek.com/xmlapi2/thing?id='+game.$.objectid+'&stats=1')
    .then(response => {
        processData(response, game);
    })
});

const processData = (response, game) => {
    const xml = response.text(); 
    XML2JS.parseString(xml, (err, result) => { 
            game.statistics = result.items.item[0].statistics[0].ratings[0];
    })
    console.log('game', game);
    console.log('STATS!', game.statistics);
}

或者您可以明确告诉它等待异步任务完成后再继续。这将要求您使用 Promise 或将整个游戏 foreach 循环包装在异步函数中。这是因为只有异步函数知道如何处理在其内部调用的另一个异步函数上的等待。


更新问题的代码

编辑器不再让我正确格式化代码,但本质上最简单的解决方案是您的所有数据处理逻辑都应该在 XML 回调中执行。从您的共享代码中,我看不到任何要求它存在于回调之外,在数据被检索后处理数据的地方。

const recursiveFetchAndWait = useCallback(
        (url) => {
            setLoading(true);
    
            fetch(url)
                .then(async response => {
                    if (response.status === 200) { // Checking for response code 200
                        const xml = await response.text();
                        setLoading(false);
                        return XML2JS.parseString(xml, (err, result) => { // xml2js: converts XML to JSON
                            if (result.items.$.totalitems !== '0') { // Only processing further if there are returned results
                                result.items.item.forEach(game => {
                                    /* Fetching the statistics from a separate API, because the base API doesn't include these */
                                    const gameId = game.$.objectid;
                                    fetch('https://cors-anywhere.herokuapp.com/https://www.boardgamegeek.com/xmlapi2/thing?id=' + gameId + '&stats=1')
                                        .then(async response => {
                                            const xml = await response.text();
                                            return XML2JS.parseString(xml, (err, result) => {

                                                // BEGINNING OF "XML2JS.parseString"
                                                console.log('result', result); // This returns data.
                                                game.statistics = result.items.item[0].statistics[0].ratings[0];
                                                // setStatistics(...{statistics}, ...{gameId: result.items.item[0].statistics[0].ratings[0]})
                                               console.log('game', game); // This returns the object with the newly statistics key.
                                               console.log('STATS!', game.statistics); // This doesn't recognize the statistics key?!
        
                                               /* Going through the array and changing default values and converting string numbers to actual numbers */
                                              if (game.stats[0].rating[0].ranks[0].rank[0].$.value === 'Not Ranked')
                    game.stats[0].rating[0].ranks[0].rank[0].$.value = 'N/A';
                                              else {
                                            game.stats[0].rating[0].ranks[0].rank[0].$.value = Number(game.stats[0].rating[0].ranks[0].rank[0].$.value);
                                              }
        
                                           game.stats[0].$.minplayers = Number(game.stats[0].$.minplayers);
                                              if (isNaN(game.stats[0].$.minplayers))
                                            game.stats[0].$.minplayers = '--';
        
                                        game.stats[0].$.maxplayers = Number(game.stats[0].$.maxplayers);
                                        if (isNaN(game.stats[0].$.maxplayers))
                                            game.stats[0].$.maxplayers = '--';
        
                                        game.stats[0].$.maxplaytime = Number(game.stats[0].$.maxplaytime);
                                        if (isNaN(game.stats[0].$.maxplaytime))
                                            game.stats[0].$.maxplaytime = '--';
        
                                        if (game.yearpublished === undefined)
                                            game.yearpublished = ['--'];
                                    });
                                    setGameList(game); // The forEach means that result.items.item == game
                                    // END OF "XML2JS.parseString" 

                                            })
                                        })
    
                            }
                        });
                    } else if (response.status === 202) { // If the status response was 202 (API still retrieving data), call the fetch again after a set timeout
                        setTimeoutAsCallback(() => recursiveFetchAndWait(url));
                    } else
                        console.log(response.status);
                })
        },
        [],
    ); 
于 2020-07-27T00:36:02.983 回答
0

如果你想创建一个顺序流,你应该遵循如下:

await Promise.all(games.map(async game => {
    await new Promise((resolve) => {
        fetch('https://www.boardgamegeek.com/xmlapi2/thing?id=' + game.$.objectid + '&stats=1')
            .then(async response => {
                const xml = await response.text(); // XML2JS boilerplate

                xml2js.parseString(xml, (err, result) => { // XML2JS boilerplate
                    console.log('result', result); // This returns data.
                    
                    game.statistics = result.items.item[0].statistics[0].ratings[0]; // Creating a new statistics key on the game object and assigning it the statistics from the API call

                    resolve();
                });
            });
        });
    }));


    games.forEach(game => {
      console.log('game', game);
      console.log('STATS!', game.statistics); 
    });
})();
于 2020-07-27T00:50:14.420 回答