{"record":{"id":"62af2848b55ffc18","repo":"jackwener/OpenCLI","slug":"no-data-62af28","errorCode":"NO_DATA","errorMessage":"eastmoney returned no ETF data","messagePattern":"eastmoney returned no ETF data","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"warning","filePath":"clis/eastmoney/etf.js","lineNumber":52,"sourceCode":"    const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));\n\n    const url = new URL('https://push2.eastmoney.com/api/qt/clist/get');\n    url.searchParams.set('pn', '1');\n    url.searchParams.set('pz', String(limit));\n    url.searchParams.set('po', sort.order === 'desc' ? '1' : '0');\n    url.searchParams.set('np', '1');\n    url.searchParams.set('fltt', '2');\n    url.searchParams.set('invt', '2');\n    url.searchParams.set('fid', sort.fid);\n    url.searchParams.set('fs', 'b:MK0021'); // 场内ETF\n    url.searchParams.set('fields', 'f12,f14,f2,f3,f4,f5,f6,f8');\n    url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');\n\n    const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n    if (!resp.ok) throw new CliError('HTTP_ERROR', `etf failed: HTTP ${resp.status}`);\n    const data = await resp.json();\n    const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];\n    if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no ETF data');\n\n    return diff.slice(0, limit).map((it, i) => ({\n      rank: i + 1,\n      code: it.f12,\n      name: it.f14,\n      price: it.f2,\n      changePercent: it.f3,\n      change: it.f4,\n      turnover: it.f6,\n      volume: it.f5,\n      turnoverRate: it.f8,\n    }));\n  },\n});\n","sourceCodeStart":34,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/etf.js#L34-L67","documentation":"CliError('NO_DATA') thrown in clis/eastmoney/etf.js when the eastmoney ETF ranking API returns HTTP 200 but data.data.diff is missing, not an array, or empty after Array.isArray normalization. The library distinguishes a well-formed-but-empty payload from HTTP failures. It means eastmoney answered successfully but had no 场内ETF (b:MK0021) rows to return.","triggerScenarios":"`resp.ok` is true but `data?.data?.diff` is undefined or `[]` at clis/eastmoney/etf.js:52 — e.g. body `{data: null}`, `{\"data\":{\"diff\":[]}}`, or an error-shaped JSON with rc != 0.","commonSituations":"eastmoney silently changing the fs=b:MK0021 universe or fields list, an expired ut token yielding an empty success payload, querying during a data refresh/maintenance window, or the diff key moving in an API schema change.","solutions":["Inspect the raw JSON body to see whether data or diff changed shape.","Verify fs=b:MK0021 and the fields list still return rows by calling the URL manually.","Retry later — eastmoney sometimes returns empty payloads during maintenance windows.","If diff moved, update the parsing path (data.data.diff) to the new location.","Enrich the error with rc / a body snippet to distinguish upstream rejection from a truly empty universe."],"exampleFix":"// before\nconst diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];\nif (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no ETF data');\n// after\nconst diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];\nif (diff.length === 0) throw new CliError('NO_DATA', `eastmoney returned no ETF data (rc=${data?.rc})`);","handlingStrategy":"fallback","validationCode":"// nothing to validate locally; validate the payload shape right after the response\nif (data?.rc !== undefined && data.rc !== 0) throw new CliError('UPSTREAM_REJECTED', `eastmoney rc=${data.rc}`);","typeGuard":"function hasEtfRows(data) {\n  return data != null && typeof data === 'object'\n    && Array.isArray(data?.data?.diff)\n    && data.data.diff.length > 0;\n}","tryCatchPattern":"try {\n  const rows = await fetchEtf({ limit: 10 });\n} catch (err) {\n  if (err instanceof CliError && err.code === 'NO_DATA') {\n    console.warn('eastmoney returned no ETF rows; using cached fallback dataset');\n    return loadCachedEtf();\n  }\n  throw err;\n}","preventionTips":["Cache the last successful ETF snapshot and fall back to it on NO_DATA.","Check data.rc / body shape when NO_DATA fires to detect silent schema changes.","Avoid querying during known eastmoney maintenance/refresh windows.","Alert on repeated NO_DATA — it usually means the fs universe or diff key changed, not a transient blip.","Log a truncated response body with the error for faster diagnosis."],"tags":["empty-data","eastmoney","api","no-data"],"backgroundTag":"empty-response-payload","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}