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

如何拆分Promise批次以规避TMDB API请求频率限制?

问题

需发起约1500次TMDB API请求,但TMDB限制每秒最多50次请求。当前用Promise.all批量请求时频繁触发429(请求过多)错误,需求是调整代码,通过分批请求或添加延迟的方式完成所有请求,最终筛选出土耳其(TR)剧集里的同名角色。

现有流程:

  1. 先请求78页土耳其剧集数据,获取所有剧集ID
  2. 再针对每个剧集请求聚合演职人员信息,筛选匹配的角色

现有代码
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:05:05