【问题标题】:Why is my UseEffect for making requests to Spotify API giving an error 429?为什么我向 Spotify API 发出请求的 UseEffect 会出现错误 429?
【发布时间】:2021-08-23 14:44:33
【问题描述】:

我目前正在制作一个 Spotify 克隆,它可以让用户预览歌曲。当我发出许多不同的 api 请求时,就会出现问题。当页面有多个请求时,会抛出 429 错误(一次请求太多)。

请通读整个问题,因为我在下面提到了我为解决此问题而采取的步骤。

Profile.js

    const { api, refreshableCall } = useSpotify()
    const [error, setError] = useState(null)
    const [userName, setUserName] = useState("")
    const [userFollowers, setUserFollowers] = useState("")
    const [userImage, setUserImage] = useState([])
    const [userLink, setUserLink] = useState("")
    const [userId, setUserId] = useState("")
    const [userFollowing, setUserFollowing] = useState("")
    const [userTopArtists, setUserTopArtists] = useState([])
    const [userTopSongs, setUserTopSongs] = useState([])

    useEffect(() => {
        let disposed = false
        refreshableCall(() => api.getMyTopTracks({
            limit: 10,
            time_range: "long_term"
        }))
            .then((res) => {
                if (disposed) return
                setUserTopSongs(res.body.items)
                setError(null)
            })
            .catch((err) => {
                if (disposed) return
                setUserTopSongs([])
                setError(err)
            });
        return () => disposed = true
    })

    useEffect(() => {
        let disposed = false
        refreshableCall(() => api.getMe())
            .then((res) => {
                if (disposed) return
                var data = res.body
                setUserName(data.display_name)
                setUserImage(data.images)
                setUserFollowers(data.followers["total"])
                setUserLink(data.external_urls.spotify)
                setUserId(data.id)
                setError(null)
            })
            .catch((err) => {
                if (disposed) return
                setUserName("")
                setUserImage([])
                setUserFollowers("")
                setUserLink("")
                setUserId("")
                setError(err)
            });
        return () => disposed = true

    })

    useEffect(() => {
        let disposed = false
        refreshableCall(() => api.getFollowedArtists())
            .then((res) => {
                if (disposed) return
                var data = res.body
                var artists = data.artists
                setUserFollowing(artists.total)
            })
            .catch((err) => {
                if (disposed) return
                setUserFollowing([])
                setError(err)
            });
        return () => disposed = true

    })

    useEffect(() => {
        let disposed = false
        refreshableCall(() => api.getMyTopArtists({
            limit: 10,
            time_range: "long_term"
        }))
            .then((res) => {
                if (disposed) return
                var data = res.body
                var artists = data.items
                setUserTopArtists(artists)
                setError(null)
            })
            .catch((err) => {
                if (disposed) return
                setUserTopArtists([])
                setError(err)
            });
        return () => disposed = true

    })

SpotifyContext.js

import React, { useState, useEffect, useContext } from "react"
import axios from "axios"
import SpotifyWebApi from 'spotify-web-api-node';

const spotifyApi = new SpotifyWebApi({
    clientId: 1234567890,
});

export const SpotifyAuthContext = React.createContext({
    exchangeCode: () => { throw new Error("context not loaded") },
    refreshAccessToken: () => { throw new Error("context not loaded") },
    hasToken: spotifyApi.getAccessToken() !== undefined,
    api: spotifyApi
});

export const useSpotify = () => useContext(SpotifyAuthContext);

function setStoredJSON(id, obj) {
    localStorage.setItem(id, JSON.stringify(obj));
}

function getStoredJSON(id, fallbackValue = null) {
    const storedValue = localStorage.getItem(id);
    return storedValue === null
        ? fallbackValue
        : JSON.parse(storedValue);
}

export function SpotifyAuthContextProvider({ children }) {
    const [tokenInfo, setTokenInfo] = useState(() => getStoredJSON('myApp:spotify', null))

    const hasToken = tokenInfo !== null

    useEffect(() => {
        if (tokenInfo === null) return;

        // attach tokens to `SpotifyWebApi` instance
        spotifyApi.setCredentials({
            accessToken: tokenInfo.accessToken,
            refreshToken: tokenInfo.refreshToken,
        })

        // persist tokens
        setStoredJSON('myApp:spotify', tokenInfo)
    }, [tokenInfo])

    function exchangeCode(code) {
        return axios
            .post("http://localhost:3001/login", {
                code
            })
            .then(res => {
                // TODO: Confirm whether response contains `accessToken` or `access_token`
                const { accessToken, refreshToken, expiresIn } = res.data;
                // store expiry time instead of expires in
                setTokenInfo({
                    accessToken,
                    refreshToken,
                    expiresAt: Date.now() + (expiresIn * 1000)
                });
            })
    }

    function refreshAccessToken() {
        const refreshToken = tokenInfo.refreshToken;
        return axios
            .post("http://localhost:3001/refresh", {
                refreshToken
            })
            .then(res => {
                const refreshedTokenInfo = {
                    accessToken: res.data.accessToken,
                    // some refreshes may include a new refresh token!
                    refreshToken: res.data.refreshToken || tokenInfo.refreshToken,
                    // store expiry time instead of expires in
                    expiresAt: Date.now() + (res.data.expiresIn * 1000)
                }

                setTokenInfo(refreshedTokenInfo)

                // attach tokens to `SpotifyWebApi` instance
                spotifyApi.setCredentials({
                    accessToken: refreshedTokenInfo.accessToken,
                    refreshToken: refreshedTokenInfo.refreshToken,
                })

                return refreshedTokenInfo
            })
    }

    async function refreshableCall(callApiFunc) {
        if (Date.now() > tokenInfo.expiresAt)
            await refreshAccessToken();

        try {
            return await callApiFunc()
        } catch (err) {
            if (err.name !== "WebapiAuthenticationError")
                throw err; // rethrow irrelevant errors
        }

        // if here, has an authentication error, try refreshing now
        return refreshAccessToken()
            .then(callApiFunc)
    }

    return (
        <SpotifyAuthContext.Provider value={{
            api: spotifyApi,
            exchangeCode,
            hasToken,
            refreshableCall,
            refreshAccessToken
        }}>
            {children}
        </SpotifyAuthContext.Provider>
    )
}

错误

  1. 没有依赖关系,它会不断循环和触发请求,每秒可能数百次。 (错误 429)
  2. 由于依赖关系,访问令牌似乎被忽略或回避。 (错误:WebApiAuthentication - 未提供令牌)

我试图做什么?

  1. 我尝试在单个 useEffect 中实现所有请求,但仍然出现错误。
  2. 使用和不使用依赖数组调用 useEffect

链接到 Github 代码库

https://github.com/amoghkapoor/spotify-clone

【问题讨论】:

    标签: javascript node.js reactjs spotify use-effect


    【解决方案1】:

    状态 429 表示您在特定时间窗口内拨打了太多电话。 因此,您在此特定时间段内被禁止。 请稍等片刻再重试。

    你试过了吗:

    useEffect(..., [])
    

    这保证它只会运行一次。

    【讨论】:

    • 不,不是这样,组件正在正确呈现,重新加载后 API 也可以正常工作,但我面临的问题是在 useEffectapi 请求中的单个请求中大约做了几百次。
    • 是的,我已经尝试在问题中添加一个依赖数组,但是当我添加一个依赖数组时,我得到一个不同的错误,说没有提供 access_token。
    【解决方案2】:

    你的 useEffect 调用都没有使用依赖数组,请记住如果调用 useEffect 时没有任何依赖,它会进入无限循环。要么找到应该重新运行 useEffect 钩子的依赖项或状态更改,并将其包含在依赖项数组中:

    useEffect(() => { /* your logic */ }, [dependencies])
    

    或者如果没有依赖项,只需在组件安装后触发它:

    useEffect(() => { /* your logic */ }, [])
    

    【讨论】:

    • 正如问题和上一个答案的评论中提到的,我已经尝试添加一个依赖数组,但现在它给出了一个不同的错误,没有提供 access_token。
    • @Amogh 那么这是您应该修复的错误,因为错误明确指出no access_token has been provided 这意味着您没有在某处提供访问令牌。不提供依赖数组无论如何都不会解决您的问题,因为它是错误的反应代码。在您的 spotify 上下文中,确保您的 accessToken 有效。
    猜你喜欢
    • 2021-12-09
    • 2022-01-10
    • 1970-01-01
    • 2019-08-22
    • 2022-01-19
    • 2021-04-03
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多