jackwener/OpenCLI · warning · CliError

NO_DATA

NO_DATA

Error message

eastmoney returned no ETF data

What it means

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.

Source

Thrown at clis/eastmoney/etf.js:52

    const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));

    const url = new URL('https://push2.eastmoney.com/api/qt/clist/get');
    url.searchParams.set('pn', '1');
    url.searchParams.set('pz', String(limit));
    url.searchParams.set('po', sort.order === 'desc' ? '1' : '0');
    url.searchParams.set('np', '1');
    url.searchParams.set('fltt', '2');
    url.searchParams.set('invt', '2');
    url.searchParams.set('fid', sort.fid);
    url.searchParams.set('fs', 'b:MK0021'); // 场内ETF
    url.searchParams.set('fields', 'f12,f14,f2,f3,f4,f5,f6,f8');
    url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');

    const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
    if (!resp.ok) throw new CliError('HTTP_ERROR', `etf failed: HTTP ${resp.status}`);
    const data = await resp.json();
    const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];
    if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no ETF data');

    return diff.slice(0, limit).map((it, i) => ({
      rank: i + 1,
      code: it.f12,
      name: it.f14,
      price: it.f2,
      changePercent: it.f3,
      change: it.f4,
      turnover: it.f6,
      volume: it.f5,
      turnoverRate: it.f8,
    }));
  },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw JSON body to see whether data or diff changed shape.
  2. Verify fs=b:MK0021 and the fields list still return rows by calling the URL manually.
  3. Retry later — eastmoney sometimes returns empty payloads during maintenance windows.
  4. If diff moved, update the parsing path (data.data.diff) to the new location.
  5. Enrich the error with rc / a body snippet to distinguish upstream rejection from a truly empty universe.

Example fix

// before
const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];
if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no ETF data');
// after
const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];
if (diff.length === 0) throw new CliError('NO_DATA', `eastmoney returned no ETF data (rc=${data?.rc})`);
Defensive patterns

Strategy: fallback

Validate before calling

// nothing to validate locally; validate the payload shape right after the response
if (data?.rc !== undefined && data.rc !== 0) throw new CliError('UPSTREAM_REJECTED', `eastmoney rc=${data.rc}`);

Type guard

function hasEtfRows(data) {
  return data != null && typeof data === 'object'
    && Array.isArray(data?.data?.diff)
    && data.data.diff.length > 0;
}

Try / catch

try {
  const rows = await fetchEtf({ limit: 10 });
} catch (err) {
  if (err instanceof CliError && err.code === 'NO_DATA') {
    console.warn('eastmoney returned no ETF rows; using cached fallback dataset');
    return loadCachedEtf();
  }
  throw err;
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/62af2848b55ffc18. Report an issue: GitHub.