错误处理
本指南详细介绍 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
解决方法:
- 检查 Token 是否正确复制 (无多余空格)
- 确认账号已激活
- 检查 Token 是否已过期
- 重新获取 Token
场景 2: 请求频率超限
错误信息: RATE_LIMIT: Request rate limit exceeded
解决方法:
- 配置并发控制:
1const client = new TushareClient({
2 token: 'YOUR_TOKEN',
3 concurrency: {
4 maxConcurrent: 1,
5 minInterval: 1000 // 1 次/秒
6 }
7});
- 增加积分等级
- 启用缓存减少重复请求
场景 3: 参数格式错误
错误信息: VALIDATION_ERROR: Invalid date format
解决方法:
- 日期格式必须为 YYYYMMDD (如
'20240131')
- 股票代码格式为 TS 格式 (如
'000001.SZ')
- 检查 API 文档确认参数要求
场景 4: 网络超时
错误信息: TIMEOUT_ERROR: Request timeout
解决方法:
- 增加超时时间:
1const client = new TushareClient({
2 token: 'YOUR_TOKEN',
3 timeout: 60000 // 60秒
4});
- 减少查询数据量 (使用日期范围过滤)
- 检查网络连接
下一步