Deno运行Supabase边缘函数时'unexpected EOF during chunk size line'错误排查
问题
运行基于Deno的Supabase边缘函数时触发以下错误:
CPU time limit reached. isolate: 16597602940236451129 CPU time used: 560ms hyper::Error(User(Body), hyper::Error(Body, Custom { kind: UnexpectedEof, error: "unexpected EOF during chunk size line" }))
函数代码如下:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { corsHeaders } from "../_shared/cors.ts"; function fetchArtistPage(artistName: string): Promise<string> { return new Promise((resolve, reject) => { setTimeout(async () => { try { const response = await fetch("https://www.last.fm/music/" + artistName.replace(" ", "+")); const page = await response.text(); resolve(page); } catch (error) { reject(error); } }, 1000); }); } serve(async (req) => { if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); const textEncoder = new TextEncoder(); const artistsNames = await req.json(); const body = new ReadableStream({ async start(controller) { for (const artistName of artistsNames) { controller.enqueue(textEncoder.encode("START_OF_JSON ")); const artistsData = { name: artistName, page: await fetchArtistPage(artistName), }; controller.enqueue(textEncoder.encode(JSON.stringify(artistsData))); controller.enqueue(textEncoder.encode("END_OF_JSON ")); } controller.close(); }, }); return new Response(body, { headers: { ...corsHeaders, "Content-Type": "text/event-stream", }, }); });
背景说明:
artistsNames是最多包含50个艺术家名称的数组,需获取对应last.fm页面内容并返回- 处理35-40个艺术家时触发错误,仅处理10个无异常
- 仅返回文本时正常,解析HTML标签内容会复现问题
- 已尝试遵循Server Side Events规范的
data和event字段,问题未解决
解决方案
1. 移除不必要延迟,缩短串行处理耗时
fetchArtistPage中的setTimeout完全多余,会额外增加1秒等待时间,直接移除即可:
function fetchArtistPage(artistName: string): Promise<string> { return fetch(`https://www.last.fm/music/${artistName.replace(" ", "+")}`) .then(res => res.text()) .catch(err => Promise.reject(err)); }
2. 控制并发请求数,避免CPU累计超时
当前代码串行逐个请求last.fm,50个请求的CPU时间会累计叠加,容易触发Supabase边缘函数的CPU限制(默认通常为500ms)。可通过并发控制减少总处理时间,同时避免被last.fm限流:
// 并发批量处理函数 async function batchProcess<T>(items: T[], batchSize: number, handler: (item: T) => Promise<any>) { 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(handler)); results.push(...batchResults); } return results; }
在流处理中调用该函数:
async start(controller) { // 每次处理5个请求,可根据实际情况调整 await batchProcess(artistsNames, 5, async (artistName) => { try { const page = await fetchArtistPage(artistName); const artistsData = { name: artistName, page }; // 用标准SSE格式发送数据 controller.enqueue(textEncoder.encode(`data: ${JSON.stringify(artistsData)}\n\n`)); } catch (err) { // 发送错误事件 controller.enqueue(textEncoder.encode(`event: error\ndata: ${JSON.stringify({name: artistName, error: err.message})}\n\n`)); } }); controller.close(); }
3. 优化CPU占用:避免返回完整HTML(非必需时)
如果不需要返回整个last.fm页面,而是提取特定内容(如艺术家简介、图片等),不要直接序列化完整HTML字符串。使用轻量HTML解析库提取所需部分,可大幅减少CPU处理时间和数据传输量。
4. 规范SSE格式,解决EOF错误
之前的自定义分隔符START_OF_JSON/END_OF_JSON不符合SSE规范,易导致客户端解析异常触发EOF错误。改用标准SSE格式:
- 每条数据以
data:开头 - 事件结尾用
\n\n分隔 - 错误事件可指定
event: error字段
5. 添加请求超时
给fetch请求添加超时限制,避免单个请求耗时过长拖垮整个函数:
function fetchArtistPage(artistName: string): Promise<string> { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时 return fetch(`https://www.last.fm/music/${artistName.replace(" ", "+")}`, { signal: controller.signal }) .then(res => { clearTimeout(timeoutId); if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); return res.text(); }) .catch(err => { clearTimeout(timeoutId); return Promise.reject(err); }); }
内容的提问来源于stack exchange,提问作者guaxuma
相关产品推荐
相关产品推荐

