错误处理

API 错误统一返回 success: false、可展示的 error 和用于程序判断的 code

错误结构

{
  "success": false,
  "error": "错误描述",
  "code": "ERROR_CODE",
  "message": "补充说明(可选)",
  "rateLimit": {
    "remaining": 147,
    "limit": 150,
    "reset": "2026-08-24T16:00:00.000Z"
  }
}

错误码

  • Name
    INVALID_PARAMETER
    Type
    400
    Description

    GET 的 word 为空或超过 100 个字符;批量请求不是对象、words 为空、超过 50 项,或数组内含无效词条。

  • Name
    NOT_FOUND
    Type
    404
    Description

    没有完整记忆卡片,并且未启用或未命中本地词典兜底。

  • Name
    RATE_LIMIT_EXCEEDED
    Type
    429
    Description

    当前 IP 的每日公开 API 配额已经用尽。

  • Name
    INSUFFICIENT_QUOTA
    Type
    429
    Description

    批量请求需要的配额多于当前剩余配额。

  • Name
    INTERNAL_ERROR
    Type
    500
    Description

    服务器内部错误。

  • Name
    NETWORK_ERROR
    Type
    client
    Description

    这不是服务端错误码,而是调用方可自行定义的网络失败状态。

推荐处理方式

async function queryWord(word, lang = 'en') {
  const params = new URLSearchParams({
    word,
    lang,
    includeLocalDictionaryFallback: 'true',
  })

  let response
  try {
    response = await fetch(
      `https://api.keykey.im/api/public/memory-card?${params}`,
      { cache: 'no-store' },
    )
  } catch (error) {
    return {
      success: false,
      code: 'NETWORK_ERROR',
      error: '网络连接失败,请稍后重试',
    }
  }

  const payload = await response.json().catch(() => ({
    success: false,
    code: 'INVALID_RESPONSE',
    error: '服务器返回了无法解析的数据',
  }))

  if (response.ok && payload.success) {
    return payload
  }

  switch (payload.code) {
    case 'NOT_FOUND':
      return { ...payload, error: '未找到匹配词条' }
    case 'RATE_LIMIT_EXCEEDED':
    case 'INSUFFICIENT_QUOTA':
      return {
        ...payload,
        error: `公开 API 配额不足,将在 ${payload.rateLimit?.reset ?? '稍后'} 重置`,
      }
    case 'INVALID_PARAMETER':
      return { ...payload, error: '请输入有效词条' }
    default:
      return { ...payload, error: payload.error || '查词失败,请稍后重试' }
  }
}

何时重试

  • NOT_FOUNDINVALID_PARAMETER:不要自动重试,修改输入或启用本地词典兜底。
  • RATE_LIMIT_EXCEEDEDINSUFFICIENT_QUOTA:等待 rateLimit.reset,不要立即重试。
  • INTERNAL_ERROR、网络超时或连接失败:可使用最多 2-3 次、带抖动的指数退避。
async function fetchWithRetry(url, attempts = 3) {
  for (let attempt = 0; attempt < attempts; attempt += 1) {
    try {
      const response = await fetch(url)
      const payload = await response.json()

      if (response.ok || response.status < 500) return payload
    } catch (error) {
      if (attempt === attempts - 1) throw error
    }

    const delay = 500 * 2 ** attempt + Math.random() * 250
    await new Promise((resolve) => setTimeout(resolve, delay))
  }
}

安全提示

将服务端的 error 当作普通文本展示,不要使用 dangerouslySetInnerHTML。记录问题时可保存 HTTP 状态、code 和请求语言,但不应记录用户的身份凭证或不必要的网络信息。

Was this page helpful?