对象 属性 在使用异步获取分配后出现未定义

Object property coming up as undefined after assigning with an async fetch

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

    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 的结果: image

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

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

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

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

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

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

或者您可以明确告诉它等待异步任务完成后再继续。这将要求您使用 promises 或将整个游戏的 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);
                })
        },
        [],
    ); 

如果要创建顺序流,应遵循以下步骤:

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