You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在含React useEffect钩子的Promise函数中使用await关键字

问题描述

我正在开发一个项目,需要通过Spotify API获取歌曲ID。我想调用一个返回Promise(包含Spotify歌曲ID列表)的函数,用await等待它完成,再用返回的数据创建新播放列表。

当我在非await上下文调用CurrentPlaylists()并让它返回歌曲数组时,函数运行正常。但尝试让它返回Promise并等待执行完毕时,出现了以下错误:

Uncaught Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

以下是我的代码:

调用CurrentPlaylists()并等待返回的函数

//top level function
export const MergePlaylists = async () => {
    const CurrentSongList = await CurrentPlaylists();

    console.log("Cur: " + CurrentSongList.length);
    
}

获取当前用户所有播放列表歌曲ID并返回Promise的函数

function CurrentPlaylists() {
    const [playlistsData, setPlaylistsData] = useState(null);
    const [playlists, setPlaylists] = useState(null);

    const PIDS = [];
    const songIDs = [];

    //retrieve first 20 playlists
    useEffect(() =>  {
        const fetchData = async () => {
            const { data } = await getCurrentUserPlaylists();
            
            setPlaylists(playlists => ([
            ...playlists ? playlists : [],
            ...data.items
            ]));

            setPlaylistsData(data);
        };
        //console.log(playlistsData);
        catchErrors(fetchData());
        }, []);

        // When playlistsData updates, check if there are more playlists to fetch
        // then update the state variable
    useEffect(() => {
        if (!playlistsData) {
            return;
        }

        // Playlist endpoint only returns 20 playlists at a time, so we need to
        // make sure we get ALL playlists by fetching the next set of playlists
        const fetchMoreData = async () => {
            if (playlistsData.next) {
                //as long as there is more data to be retrieved
                const { data } = await axios.get(playlistsData.next);
                
                setPlaylists(playlists => ([
                    ...playlists ? playlists : [],
                    ...data.items
                    ]));

                setPlaylistsData(data);
            } else {
            
            if(playlists !== null) { 
                //extract playlist ID from playlist into array
                Object.entries(playlists).forEach((key, value) => {
                    const PID = key[1]["id"];

                    if (!PIDS.includes(PID)) { 
                        PIDS.push(PID);
                    }
                });
                //for each playlist, 
                for (var PID in PIDS) {
                    const id = PIDS[PID];
                    
                    //listData contains first 100 songs
                    let listData = await getPlaylistItems(id);
                    
                    //retrieve songs from specified playlist 
                    Object.entries(listData.data.items).forEach((key, value) => {
                        const songID = key[1]["track"]["id"];
                        if (!songIDs.includes(songID) && songID !== null) { songIDs.push(songID); }
                    });
                    //console.log("before while: " + i);
                    songIDs.push(listData.data.items);
                    
                    while (listData.data.next) {

                        listData = await axios.get(listData.data.next);
                        console.log("nexted");
                        
                        Object.entries(listData.data.items).forEach((key, value) => {
                            const songID = key[1]["track"]["id"];
                            if (!songIDs.includes(songID) && songID !== null) { songIDs.push(songID); }
                        })
                        songIDs.push(listData.data.items);
                        
                    }
                }
                console.log(songIDs.length);

                //return a promise containing the array of songIDs
                return new Promise((resolve, reject) => {
                    resolve(songIDs);
                });
            }
            }
        };

        catchErrors(fetchMoreData());

        }, [playlistsData]);
};
问题原因

CurrentPlaylists是一个React组件(因为它使用了useState、useEffect这些React Hooks),组件的本质是返回React元素的函数,不能像普通异步函数那样返回Promise。当你用await CurrentPlaylists()时,实际上是在等待一个React组件实例,而非你期望的歌曲ID数组Promise,这就触发了React的错误——不能把Promise作为React子元素渲染。

另外,组件内部useEffect中的异步函数返回的Promise无法传递到组件外部,因为useEffect的回调函数返回值要么是清理函数,要么是undefined,内部的返回值不会被组件本身接收。

解决方案

把数据获取逻辑从React组件中抽离出来,写成独立的异步函数,完全脱离React Hooks的约束。如果需要在组件中使用这些数据,再在组件内部调用这个函数并更新状态。

修改后的独立数据获取函数

// 独立的异步函数,负责获取所有歌曲ID
export async function getAllUserSongIDs() {
    const songIDs = [];
    const PIDS = [];

    // 获取第一页播放列表
    let { data: playlistsData } = await getCurrentUserPlaylists();
    let allPlaylists = [...playlistsData.items];

    // 循环获取所有播放列表(处理分页)
    while (playlistsData.next) {
        const { data } = await axios.get(playlistsData.next);
        allPlaylists = [...allPlaylists, ...data.items];
        playlistsData = data;
    }

    // 提取所有不重复的播放列表ID
    allPlaylists.forEach(playlist => {
        if (!PIDS.includes(playlist.id)) {
            PIDS.push(playlist.id);
        }
    });

    // 遍历每个播放列表,获取所有歌曲ID
    for (const pid of PIDS) {
        let listData = await getPlaylistItems(pid);
        
        // 提取当前页歌曲ID
        listData.data.items.forEach(item => {
            const songID = item.track?.id;
            if (songID && !songIDs.includes(songID)) {
                songIDs.push(songID);
            }
        });

        // 处理歌曲分页
        while (listData.data.next) {
            listData = await axios.get(listData.data.next);
            listData.data.items.forEach(item => {
                const songID = item.track?.id;
                if (songID && !songIDs.includes(songID)) {
                    songIDs.push(songID);
                }
            });
        }
    }

    return songIDs;
}

修改调用函数

export const MergePlaylists = async () => {
    const CurrentSongList = await getAllUserSongIDs();
    console.log("Cur: " + CurrentSongList.length);
    // 在这里用CurrentSongList创建新播放列表
}

如果需要在React组件中使用该数据

import { getAllUserSongIDs } from './path-to-your-function';

function YourComponent() {
    const [songIDs, setSongIDs] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const fetchSongIDs = async () => {
            try {
                const ids = await getAllUserSongIDs();
                setSongIDs(ids);
            } catch (err) {
                console.error('获取歌曲ID失败:', err);
            } finally {
                setLoading(false);
            }
        };

        fetchSongIDs();
    }, []);

    if (loading) return <div>加载中...</div>;

    return (
        <div>
            {/* 在这里渲染songIDs相关内容 */}
            <p>共获取到{songIDs.length}首歌曲</p>
        </div>
    );
}
额外优化点
  • 原代码中Object.entries(playlists).forEach可以简化为playlists.forEach,因为playlists是数组,直接遍历元素即可,不需要用Object.entries。
  • 原代码中songIDs.push(listData.data.items)是错误的,这会把整个歌曲对象数组推入songIDs,而我们只需要ID,所以删除这行。
  • 使用可选链item.track?.id避免track为null时的报错。

内容的提问来源于stack exchange,提问作者jade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 03:33:27