错误处理

本指南详细介绍 Tushare SDK 中的错误类型、错误处理机制和最佳实践。

错误类型

SDK 定义了 ApiError 类和 ApiErrorType 枚举来表示不同类型的错误。

ApiErrorType 枚举

1enum ApiErrorType {
2  AUTH_ERROR = 'AUTH_ERROR',           // 认证错误
3  RATE_LIMIT = 'RATE_LIMIT',           // 限流错误
4  NETWORK_ERROR = 'NETWORK_ERROR',     // 网络错误
5  SERVER_ERROR = 'SERVER_ERROR',       // 服务器错误
6  VALIDATION_ERROR = 'VALIDATION_ERROR', // 参数验证错误
7  TIMEOUT_ERROR = 'TIMEOUT_ERROR',     // 超时错误
8  UNKNOWN_ERROR = 'UNKNOWN_ERROR'      // 未知错误
9}

错误类型说明

错误类型 HTTP状态码 可重试 说明 常见原因
AUTH_ERROR 401, 403 认证错误 Token 无效或过期
RATE_LIMIT 429 限流错误 请求频率超限
NETWORK_ERROR - 网络错误 网络连接失败
SERVER_ERROR 500, 502, 503, 504 服务器错误 Tushare 服务器内部错误
VALIDATION_ERROR 400 参数验证错误 请求参数不合法
TIMEOUT_ERROR - 超时错误 请求超时
UNKNOWN_ERROR 其他 未知错误 其他未分类错误

ApiError 类

类定义

1class ApiError extends Error {
2  type: ApiErrorType;           // 错误类型
3  message: string;              // 错误消息
4  code?: number;                // HTTP 状态码 (如适用)
5  originalError?: Error;        // 原始错误对象
6  retryable: boolean;           // 是否可重试 (自动计算)
7  retryAfter?: number;          // 建议重试延迟 (毫秒)
8}

属性说明

属性 类型 说明
type ApiErrorType 错误类型枚举值
message string 人类可读的错误消息
code number | undefined HTTP 状态码 (网络错误为 undefined)
originalError Error | undefined 原始的 JavaScript Error 对象
retryable boolean 是否可以自动重试
retryAfter number | undefined 建议的重试延迟时间 (毫秒)

捕获错误

基本错误捕获

使用 try-catch 捕获所有错误:

1import { TushareClient, ApiError } from '@hestudy/tushare-sdk';
2
3const client = new TushareClient({ token: 'YOUR_TOKEN' });
4
5try {
6  const stocks = await client.getStockBasic();
7  console.log(stocks);
8} catch (error) {
9  if (error instanceof ApiError) {
10    console.error(`错误类型: ${error.type}`);
11    console.error(`错误消息: ${error.message}`);
12    console.error(`HTTP 状态码: ${error.code}`);
13    console.error(`是否可重试: ${error.retryable}`);
14  } else {
15    console.error('未知错误:', error);
16  }
17}

访问错误详情

1try {
2  const stocks = await client.getStockBasic();
3} catch (error) {
4  if (error instanceof ApiError) {
5    // 基本信息
6    console.log('错误类型:', error.type);
7    console.log('错误消息:', error.message);
8
9    // HTTP 信息 (如果有)
10    if (error.code) {
11      console.log('HTTP 状态码:', error.code);
12    }
13
14    // 重试信息
15    if (error.retryable) {
16      console.log('该错误可以重试');
17      if (error.retryAfter) {
18        console.log(`建议等待 ${error.retryAfter}ms 后重试`);
19      }
20    }
21
22    // 原始错误 (调试用)
23    if (error.originalError) {
24      console.log('原始错误:', error.originalError);
25    }
26  }
27}

处理特定错误

认证错误 (AUTH_ERROR)

Token 无效或过期时触发:

1import { TushareClient, ApiError, ApiErrorType } from '@hestudy/tushare-sdk';
2
3const client = new TushareClient({ token: 'YOUR_TOKEN' });
4
5try {
6  const stocks = await client.getStockBasic();
7} catch (error) {
8  if (error instanceof ApiError && error.type === ApiErrorType.AUTH_ERROR) {
9    console.error('Token 无效,请检查:');
10    console.error('1. Token 是否正确复制 (无多余空格)');
11    console.error('2. 账号是否已激活');
12    console.error('3. Token 是否已过期');
13
14    // 提示用户重新配置 Token
15    process.exit(1);
16  }
17}

限流错误 (RATE_LIMIT)

请求频率超过积分等级限制时触发:

1try {
2  const stocks = await client.getStockBasic();
3} catch (error) {
4  if (error instanceof ApiError && error.type === ApiErrorType.RATE_LIMIT) {
5    console.log('请求频率超限');
6
7    if (error.retryAfter) {
8      console.log(`Tushare 建议等待 ${error.retryAfter}ms 后重试`);
9
10      // 可选: 等待后自动重试
11      await new Promise(resolve => setTimeout(resolve, error.retryAfter!));
12      // 重试逻辑...
13    } else {
14      console.log('建议:');
15      console.log('1. 配置并发控制 (concurrency 选项)');
16      console.log('2. 增加请求间隔时间');
17      console.log('3. 升级 Tushare 积分等级');
18    }
19  }
20}

网络错误 (NETWORK_ERROR)

网络连接失败时触发:

1try {
2  const stocks = await client.getStockBasic();
3} catch (error) {
4  if (error instanceof ApiError && error.type === ApiErrorType.NETWORK_ERROR) {
5    console.error('网络连接失败,请检查:');
6    console.error('1. 网络连接是否正常');
7    console.error('2. 是否可以访问 api.tushare.pro');
8    console.error('3. 防火墙或代理设置是否正确');
9
10    // SDK 会自动重试网络错误
11    console.log('SDK 将自动重试...');
12  }
13}

服务器错误 (SERVER_ERROR)

Tushare 服务器返回 5xx 错误时触发:

1try {
2  const stocks = await client.getStockBasic();
3} catch (error) {
4  if (error instanceof ApiError && error.type === ApiErrorType.SERVER_ERROR) {
5    console.error(`Tushare 服务器错误 (${error.code})`);
6    console.error('这通常是 Tushare 服务端的临时问题');
7    console.error('SDK 将自动重试,如果持续失败,请稍后再试');
8  }
9}

参数验证错误 (VALIDATION_ERROR)

请求参数不合法时触发:

1try {
2  const dailyData = await client.getDailyQuote({
3    // 错误: 日期格式不正确 (应该是 YYYYMMDD)
4    start_date: '2024-01-01',
5    end_date: '2024-01-31'
6  });
7} catch (error) {
8  if (error instanceof ApiError && error.type === ApiErrorType.VALIDATION_ERROR) {
9    console.error('参数验证失败:', error.message);
10    console.error('请检查:');
11    console.error('1. 参数格式是否正确 (如日期格式为 YYYYMMDD)');
12    console.error('2. 必需参数是否提供');
13    console.error('3. 参数值是否在有效范围内');
14  }
15}

超时错误 (TIMEOUT_ERROR)

请求超时时触发:

1try {
2  const stocks = await client.getStockBasic();
3} catch (error) {
4  if (error instanceof ApiError && error.type === ApiErrorType.TIMEOUT_ERROR) {
5    console.error('请求超时');
6    console.error('建议:');
7    console.error('1. 增加 timeout 配置值');
8    console.error('2. 减少查询的数据量 (使用日期范围过滤)');
9    console.error('3. 检查网络连接速度');
10
11    // SDK 会自动重试超时错误
12    console.log('SDK 将自动重试...');
13  }
14}

自动重试机制

SDK 会自动重试以下类型的错误:

  • RATE_LIMIT - 限流错误
  • NETWORK_ERROR - 网络错误
  • TIMEOUT_ERROR - 超时错误
  • SERVER_ERROR - 服务器错误

重试行为

SDK 使用指数退避算法进行重试:

delay = min(initialDelay * backoffFactor^n, maxDelay)

默认配置:

  • 最大重试次数: 3 次
  • 初始延迟: 1000ms (1秒)
  • 最大延迟: 30000ms (30秒)
  • 退避因子: 2

配置重试策略

1const client = new TushareClient({
2  token: 'YOUR_TOKEN',
3  retry: {
4    maxRetries: 5,        // 增加重试次数
5    initialDelay: 2000,   // 增加初始延迟
6    maxDelay: 60000,      // 增加最大延迟
7    backoffFactor: 2      // 保持退避因子
8  }
9});

更多重试配置选项,请查看 配置指南

禁用自动重试

1const client = new TushareClient({
2  token: 'YOUR_TOKEN',
3  retry: {
4    maxRetries: 0  // 禁用重试
5  }
6});

错误处理最佳实践

1. 始终使用 try-catch

所有 API 调用都应该包裹在 try-catch 中:

1// ✅ 推荐
2async function fetchStocks() {
3  try {
4    const stocks = await client.getStockBasic();
5    return stocks;
6  } catch (error) {
7    console.error('获取股票列表失败:', error);
8    throw error; // 或返回默认值
9  }
10}
11
12// ❌ 不推荐 - 缺少错误处理
13async function fetchStocks() {
14  const stocks = await client.getStockBasic();
15  return stocks;
16}

2. 区分可重试和不可重试错误

根据 error.retryable 属性决定是否重试:

1async function fetchWithRetry(fn: () => Promise<any>, maxAttempts = 3) {
2  let lastError: ApiError;
3
4  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5    try {
6      return await fn();
7    } catch (error) {
8      if (error instanceof ApiError) {
9        lastError = error;
10
11        // 如果错误不可重试,立即抛出
12        if (!error.retryable) {
13          throw error;
14        }
15
16        // 如果还有重试机会,等待后重试
17        if (attempt < maxAttempts) {
18          const delay = error.retryAfter || 1000 * attempt;
19          console.log(`${attempt} 次尝试失败,等待 ${delay}ms 后重试...`);
20          await new Promise(resolve => setTimeout(resolve, delay));
21          continue;
22        }
23      }
24
25      throw error;
26    }
27  }
28
29  throw lastError!;
30}
31
32// 使用
33try {
34  const stocks = await fetchWithRetry(() => client.getStockBasic());
35} catch (error) {
36  console.error('重试失败:', error);
37}

3. 记录错误日志

记录详细的错误信息便于调试:

1import { TushareClient, ApiError, ConsoleLogger, LogLevel } from '@hestudy/tushare-sdk';
2
3// 启用调试日志
4const client = new TushareClient({
5  token: 'YOUR_TOKEN',
6  logger: new ConsoleLogger(LogLevel.DEBUG)
7});
8
9try {
10  const stocks = await client.getStockBasic();
11} catch (error) {
12  if (error instanceof ApiError) {
13    // 记录完整的错误信息
14    console.error({
15      timestamp: new Date().toISOString(),
16      errorType: error.type,
17      message: error.message,
18      code: error.code,
19      retryable: error.retryable,
20      stack: error.stack
21    });
22  }
23}

4. 提供用户友好的错误消息

将技术错误转换为用户可理解的消息:

1function getUserFriendlyErrorMessage(error: ApiError): string {
2  switch (error.type) {
3    case ApiErrorType.AUTH_ERROR:
4      return '身份验证失败,请检查 API Token 是否正确';
5
6    case ApiErrorType.RATE_LIMIT:
7      return '请求过于频繁,请稍后再试';
8
9    case ApiErrorType.NETWORK_ERROR:
10      return '网络连接失败,请检查网络设置';
11
12    case ApiErrorType.SERVER_ERROR:
13      return 'Tushare 服务暂时不可用,请稍后再试';
14
15    case ApiErrorType.VALIDATION_ERROR:
16      return `参数错误: ${error.message}`;
17
18    case ApiErrorType.TIMEOUT_ERROR:
19      return '请求超时,请重试或减少查询数据量';
20
21    default:
22      return `发生错误: ${error.message}`;
23  }
24}
25
26// 使用
27try {
28  const stocks = await client.getStockBasic();
29} catch (error) {
30  if (error instanceof ApiError) {
31    const userMessage = getUserFriendlyErrorMessage(error);
32    console.error(userMessage);
33  }
34}

5. 实现降级策略

当 API 调用失败时,提供备用方案:

1async function getStocksWithFallback() {
2  try {
3    // 尝试从 API 获取
4    return await client.getStockBasic({ list_status: 'L' });
5  } catch (error) {
6    if (error instanceof ApiError) {
7      console.warn('API 调用失败,使用缓存数据:', error.message);
8
9      // 降级方案: 返回缓存数据或默认值
10      return getCachedStocks(); // 从缓存或数据库读取
11    }
12    throw error;
13  }
14}

常见错误场景

场景 1: Token 无效

错误信息: AUTH_ERROR: Invalid token

解决方法:

  1. 检查 Token 是否正确复制 (无多余空格)
  2. 确认账号已激活
  3. 检查 Token 是否已过期
  4. 重新获取 Token

场景 2: 请求频率超限

错误信息: RATE_LIMIT: Request rate limit exceeded

解决方法:

  1. 配置并发控制:
    1const client = new TushareClient({
    2  token: 'YOUR_TOKEN',
    3  concurrency: {
    4    maxConcurrent: 1,
    5    minInterval: 1000  // 1 次/秒
    6  }
    7});
  2. 增加积分等级
  3. 启用缓存减少重复请求

场景 3: 参数格式错误

错误信息: VALIDATION_ERROR: Invalid date format

解决方法:

  • 日期格式必须为 YYYYMMDD (如 '20240131')
  • 股票代码格式为 TS 格式 (如 '000001.SZ')
  • 检查 API 文档确认参数要求

场景 4: 网络超时

错误信息: TIMEOUT_ERROR: Request timeout

解决方法:

  1. 增加超时时间:
    1const client = new TushareClient({
    2  token: 'YOUR_TOKEN',
    3  timeout: 60000  // 60秒
    4});
  2. 减少查询数据量 (使用日期范围过滤)
  3. 检查网络连接

下一步