如何拆分Promise批次以规避TMDB API请求频率限制?
问题
需发起约1500次TMDB API请求,但TMDB限制每秒最多50次请求。当前用Promise.all批量请求时频繁触发429(请求过多)错误,需求是调整代码,通过分批请求或添加延迟的方式完成所有请求,最终筛选出土耳其(TR)剧集里的同名角色。
现有流程:
- 先请求78页土耳其剧集数据,获取所有剧集ID
- 再针对每个剧集请求聚合演职人员信息,筛选匹配的角色
现有代码
export default function CharSearch(){ const [input, setInput] = React.useState("") const [showIds, setShowIds] = React.useState([]) const [characters, setCharacters] = React.useState([]) const { promiseInProgress } = usePromiseTracker() React.useEffect(()=>{ Promise.all(Array.from({length: 78}, (_, i) => fetch(`https://api.themoviedb.org/3/discover/tv?api_key=${API_KEY}&with_origin_country=TR&page=${i + 1}`).then(r => r.json()) .then(data => data.results.map(show => ({name: show.original_name, id: show.id}))))) .then(res => setShowIds(res.flat())) }, []) function getChars(){ trackPromise( Promise.all(Array.from({length: showIds.length}, (_, i) => fetch(`https://api.themoviedb.org/3/tv/${showIds[i].id}/aggregate_credits?api_key=${API_KEY}`).then(r => r.json()) .then(data => data?.cast.filter(person => removeDiacritics(person.roles[0].character).startsWith(input)).map(person => ( {character: person.roles[0].character, name: person.name, show: showIds[i].name, profile: person.profile_path})) ))) .then(res => setCharacters(res.flat())) .catch((error) => { console.error(error); }) ) } function handleClick(){ getChars() } function removeDiacritics(str){ return str.normalize("NFKD").replace(/\p{Diacritic}/gu, "") } return( <div className="CharacterSearch" > <div className="search" > <div className="search-inputs"> <input type="text" value={input || ""} placeholder="Search character" onChange={((e)=>setInput(removeDiacritics(input==="" ? e.target.value.toUpperCase() : e.target.value)))}/> <button className="search-icon" disabled={promiseInProgress ? true : false} onClick={handleClick}> <SearchIcon /> </button> </div> <div style={{height: "400px", width: "800px", marginTop: "80px", textAlign: "center"}}> <h3>Characters</h3> {(promiseInProgress === true) ? <ClipLoader /> : null} {characters.map((person)=>{ return( <CharacterCard key={`${person.name}-${person.character}-${person.show}`} {...person}/> ) })} </div> </div> </div> ) }
解决方案
1. 核心思路
替换Promise.all的全量并发请求为分批可控并发,每批请求数量不超过TMDB的每秒50次限制,批次之间添加延迟;同时处理429错误的自动重试逻辑。
2. 实现分批请求工具函数
先写一个通用的分批处理函数,用于控制请求的并发数和批次间隔:
// 分批处理请求,控制并发数与批次间隔 async function batchRequests(items, batchSize, delayMs, requestFn) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); // 并行处理当前批次的所有请求 const batchResults = await Promise.all(batch.map(requestFn)); results.push(...batchResults); // 非最后一批则添加延迟 if (i + batchSize < items.length) { await new Promise(resolve => setTimeout(resolve, delayMs)); } } return results; }
3. 修改剧集ID获取逻辑
把全量请求改为分批请求,同时添加429错误重试:
React.useEffect(()=>{ const fetchShowIds = async () => { const pages = Array.from({length: 78}, (_, i) => i + 1); // 每批请求20页,间隔1秒(保守值,避免触发限流) const allResults = await batchRequests( pages, 20, 1000, async (page) => { let res = await fetch(`https://api.themoviedb.org/3/discover/tv?api_key=${API_KEY}&with_origin_country=TR&page=${page}`); // 处理429错误,按响应头指定时间重试 if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || 1); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); res = await fetch(`https://api.themoviedb.org/3/discover/tv?api_key=${API_KEY}&with_origin_country=TR&page=${page}`); } const data = await res.json(); return data.results.map(show => ({name: show.original_name, id: show.id})); } ); setShowIds(allResults.flat()); }; fetchShowIds(); }, [])
4. 修改角色搜索逻辑
同样用分批处理1560个剧集的请求,每批控制在50个(刚好符合TMDB的每秒限制):
async function getChars(){ try { const allCharacters = await batchRequests( showIds, 50, // 每批50个,匹配TMDB的每秒请求限制 1000, // 每批间隔1秒 async (show) => { let res = await fetch(`https://api.themoviedb.org/3/tv/${show.id}/aggregate_credits?api_key=${API_KEY}`); // 处理429错误重试 if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || 1); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); res = await fetch(`https://api.themoviedb.org/3/tv/${show.id}/aggregate_credits?api_key=${API_KEY}`); } const data = await res.json(); return data?.cast.filter(person => removeDiacritics(person.roles[0].character).startsWith(input)) .map(person => ({ character: person.roles[0].character, name: person.name, show: show.name, profile: person.profile_path })) || []; } ); setCharacters(allCharacters.flat()); } catch (error) { console.error(error); } } // 更新handleClick,确保trackPromise包裹整个异步流程 function handleClick(){ trackPromise(getChars()); }
内容的提问来源于stack exchange,提问作者Kay
相关产品推荐
相关产品推荐

