To 工具类文档 - 安全异步执行工具
概述
to.ts 是一个强大的异步执行工具函数集,专门用于将 Promise 和可能抛出异常的代码转换为 [error, data] 格式,避免频繁使用 try-catch,让代码更加简洁和可读。
核心理念
- 统一错误处理:所有异步操作都返回
[Error | null, T | null]格式 - 避免 try-catch 地狱:不再需要到处写 try-catch 代码块
- 类型安全:完整的 TypeScript 支持
- 功能丰富:涵盖常见的异步操作场景
核心函数
1. to() - 基础异步处理 ⭐⭐⭐⭐⭐
最重要的函数,99% 的场景都会用到
const to = async <T>(promise: Promise<T>): Promise<[Error | null, T | null]>功能描述
将任何 Promise 转换为 [error, data] 格式,避免使用 try-catch。
使用示例
// ❌ 传统写法
try {
const user = await getUserById('123');
console.log('用户信息:', user);
} catch (error) {
console.error('获取用户失败:', error.message);
return;
}
// ✅ 使用 to() 函数
const [err, user] = await to(getUserById('123'));
if (err) {
console.error('获取用户失败:', err.message);
return;
}
console.log('用户信息:', user);// API 请求处理
const [err, response] = await to(fetch('/api/data'));
if (err) {
showErrorMessage('网络请求失败');
return;
}
const [parseErr, data] = await to(response.json());
if (parseErr) {
showErrorMessage('数据解析失败');
return;
}
console.log('请求结果:', data);2. toValidate() - 表单验证专用 ⭐⭐⭐⭐
后台管理系统表单验证的利器
const toValidate = async (formRef: Ref<ElFormInstance>): Promise<[Error | null, boolean]>功能描述
专门处理 Element Plus 等 UI 库的表单验证,将回调式 API 转换为 Promise。
使用示例
// Vue 组件中的表单验证
const handleSubmit = async () => {
const [err, isValid] = await toValidate(formRef.value);
if (err || !isValid) {
ElMessage.error(err?.message || '表单验证失败');
return;
}
// 验证通过,提交表单
const [submitErr] = await to(submitForm(formData));
if (submitErr) {
ElMessage.error('提交失败: ' + submitErr.message);
return;
}
ElMessage.success('提交成功');
};// 多个表单同时验证
const validateAllForms = async () => {
const results = await toAll([
toValidate(userFormRef.value),
toValidate(addressFormRef.value),
toValidate(paymentFormRef.value)
]);
const hasErrors = results.some(([err, isValid]) => err || !isValid);
if (hasErrors) {
ElMessage.error('请检查表单输入');
return false;
}
return true;
};3. toAll() - 批量处理 Promise ⭐⭐⭐⭐
并行执行多个异步操作
const toAll = async <T>(
promises: (Promise<T> | Promise<[Error | null, T | null]>)[]
): Promise<Array<[Error | null, T | null]>>功能描述
并行执行多个 Promise,每个都使用 to 函数包装,不会因为某个失败而中断其他请求。支持混合传入原始 Promise 和已用 to() 包装的 Promise。
使用示例
// 并行请求多个接口
const promises = [
getUserInfo(userId), // 原始 Promise
to(getUserOrders(userId)), // 手动用 to() 包装的 Promise
getUserPreferences(userId) // 原始 Promise
];
const results = await toAll(promises);
const [userErr, userInfo] = results[0];
const [ordersErr, orders] = results[1];
const [prefsErr, preferences] = results[2];
// 统一处理,无论原始格式如何
if (userErr) console.error('获取用户信息失败:', userErr.message);
if (ordersErr) console.error('获取订单失败:', ordersErr.message);
if (prefsErr) console.error('获取偏好设置失败:', prefsErr.message);// 批量上传文件
const uploadPromises = files.map(file => uploadFile(file));
const results = await toAll(uploadPromises);
const successCount = results.filter(([err]) => !err).length;
const failCount = results.filter(([err]) => err).length;
console.log(`上传完成: 成功 ${successCount} 个,失败 ${failCount} 个`);4. toWithTimeout() - 超时控制 ⭐⭐⭐
为 Promise 添加超时机制
const toWithTimeout = async <T>(
promise: Promise<T>,
timeoutMs: number,
timeoutMessage?: string
): Promise<[Error | null, T | null]>功能描述
为 Promise 添加超时控制,避免长时间等待,适用于网络请求、外部服务调用等场景。
使用示例
// 为 API 请求设置 5 秒超时
const [err, data] = await toWithTimeout(
fetch('/api/heavy-computation'),
5000,
'计算超时,请稍后重试'
);
if (err) {
if (err.message.includes('超时')) {
showTimeoutMessage();
} else {
showErrorMessage('请求失败');
}
return;
}
console.log('计算结果:', data);5. toSync() - 同步版本 ⭐⭐⭐
处理可能抛出异常的同步代码
const toSync = <T>(fn: () => T): [Error | null, T | null]功能描述
用于包装可能抛出异常的同步函数,统一错误处理格式。主要用于 JSON 解析、数据处理等可能出错的同步操作。
使用示例
// 处理 JSON 解析
const [err, data] = toSync(() => JSON.parse(jsonString));
if (err) {
console.error('JSON 解析失败:', err.message);
return;
}
console.log('解析结果:', data);// 处理数组操作
const [err, result] = toSync(() => {
if (array.length === 0) throw new Error('数组为空');
return array[0].someProperty.toUpperCase();
});
if (err) {
console.error('操作失败:', err.message);
return;
}
console.log('处理结果:', result);增强功能函数
6. toWithRetry() - 自动重试 ⭐⭐⭐
适用于网络不稳定场景
const toWithRetry = async <T>(
promiseFactory: (() => Promise<T>) | (() => Promise<[Error | null, T | null]>),
maxRetries: number = 2,
retryDelay: number = 1000
): Promise<[Error | null, T | null]>使用示例
// 重试网络请求
const [err, data] = await toWithRetry(
() => fetch('/api/unstable-endpoint'),
3, // 重试 3 次
2000 // 间隔 2 秒
);
if (err) {
console.error('重试 3 次后仍然失败:', err.message);
showErrorMessage('网络连接不稳定,请稍后重试');
return;
}
console.log('请求成功:', data);7. toWithDefault() - 带默认值 ⭐⭐
失败时使用备用数据
const toWithDefault = async <T>(
promise: Promise<T>,
defaultValue: T
): Promise<[Error | null, T]>使用示例
// 获取用户配置,失败时使用默认配置
const [err, config] = await toWithDefault(
getUserConfig(userId),
{ theme: 'light', language: 'zh-CN', pageSize: 10 }
);
// config 保证不为 null,要么是获取到的配置,要么是默认配置
applyUserConfig(config);
if (err) {
console.warn('使用默认配置,原因:', err.message);
}8. toWithLog() - 带调试日志 ⭐⭐
开发阶段监控异步操作
const toWithLog = async <T>(
promise: Promise<T>,
label: string = 'Promise',
enableLog: boolean = true
): Promise<[Error | null, T | null]>使用示例
// 监控 API 请求
const [err, userData] = await toWithLog(
fetchUserData(userId),
'获取用户数据',
true
);
// 控制台输出:
// [获取用户数据] 开始执行
// [获取用户数据] 执行成功 (1234ms) 或 执行失败: 错误信息高级功能函数
9. toSequence() - 串行执行 ⭐⭐
按顺序处理有依赖关系的操作
const toSequence = async <T>(
operations: Array<(() => Promise<T>) | (() => Promise<[Error | null, T | null]>)>
): Promise<[Error | null, T[]]>使用示例
// 按顺序执行初始化步骤
const initSteps = [
() => connectToDatabase(), // 原始 Promise 函数
() => to(loadConfiguration()), // 用 to() 包装的函数
() => startServices(), // 原始 Promise 函数
() => to(scheduleJobs()) // 用 to() 包装的函数
];
const [err, results] = await toSequence(initSteps);
if (err) {
console.error('初始化失败:', err.message);
await rollbackInitialization();
return;
}
console.log('所有初始化步骤完成:', results);10. toIf() - 条件执行 ⭐
根据条件决定是否执行异步操作
const toIf = async <T>(
condition: boolean,
promise: Promise<T> | Promise<[Error | null, T | null]>
): Promise<[Error | null, T | null]>使用示例
// 根据权限决定是否加载敏感数据
const [err, sensitiveData] = await toIf(
userRole === 'admin',
fetchSensitiveUserData(userId)
);
if (err) {
console.error('加载敏感数据失败:', err.message);
} else if (sensitiveData) {
renderSensitiveDataPanel(sensitiveData);
} else {
console.log('权限不足,跳过敏感数据加载');
}11. toResult() - 类型安全透传 ⭐
用于已返回 Result 格式的函数
const toResult = async <T>(
resultPromise: Promise<[Error | null, T | null]>
): Promise<[Error | null, T | null]>使用示例
// 用于保持 API 一致性
const fetchUserSafely = async (userId: string) => {
// 这个函数已经返回 Result 格式
return toResult(apiClient.getUser(userId));
};
// 使用时保持一致的调用方式
const [err, user] = await fetchUserSafely('123');实用技巧与最佳实践
1. 错误处理策略
// ✅ 推荐:明确的错误处理
const [err, user] = await to(fetchUser(id));
if (err) {
// 根据错误类型采取不同处理策略
if (err.message.includes('网络')) {
showNetworkError();
} else if (err.message.includes('权限')) {
redirectToLogin();
} else {
showGenericError();
}
return;
}
// 使用 user 数据2. 嵌套调用处理
// ✅ 推荐:扁平化处理
const [userErr, user] = await to(fetchUser(id));
if (userErr) return handleError(userErr);
const [orderErr, orders] = await to(fetchUserOrders(user.id));
if (orderErr) return handleError(orderErr);
const [prefErr, preferences] = await to(fetchUserPreferences(user.id));
if (prefErr) return handleError(prefErr);
// 所有数据都成功获取
renderUserDashboard(user, orders, preferences);3. 表单验证组合
// ✅ 推荐:组合多种验证
const validateAndSubmit = async () => {
// 1. 表单验证
const [validateErr, isValid] = await toValidate(formRef.value);
if (validateErr || !isValid) {
showValidationErrors(validateErr?.message);
return;
}
// 2. 提交数据
const [submitErr, response] = await to(submitForm(formData));
if (submitErr) {
showSubmitError(submitErr.message);
return;
}
// 3. 成功处理
showSuccessMessage('提交成功');
router.push('/success');
};4. 并行请求优化
// ✅ 推荐:合理使用并行请求
const loadDashboardData = async () => {
const results = await toAll([
fetchUserInfo(),
fetchNotifications(),
fetchRecentActivity(),
fetchSystemStats()
]);
const [userErr, userInfo] = results[0];
const [notifyErr, notifications] = results[1];
const [activityErr, activities] = results[2];
const [statsErr, stats] = results[3];
// 部分数据失败不影响整体页面渲染
if (!userErr) setUserInfo(userInfo);
if (!notifyErr) setNotifications(notifications);
if (!activityErr) setActivities(activities);
if (!statsErr) setStats(stats);
};常见场景示例
场景 1: 用户登录流程
const handleLogin = async () => {
// 1. 表单验证
const [validateErr, isValid] = await toValidate(loginFormRef.value);
if (validateErr || !isValid) {
ElMessage.error('请检查输入信息');
return;
}
// 2. 登录请求(带超时)
showLoading('正在登录...');
const [loginErr, loginResult] = await toWithTimeout(
userLogin(loginForm),
10000,
'登录超时,请重试'
);
hideLoading();
if (loginErr) {
ElMessage.error(`登录失败: ${loginErr.message}`);
return;
}
// 3. 获取用户信息
const [userErr, userInfo] = await to(fetchUserInfo());
if (userErr) {
ElMessage.warning('登录成功,但获取用户信息失败');
} else {
setUserInfo(userInfo);
}
ElMessage.success('登录成功');
router.push('/dashboard');
};场景 2: 文件上传处理
const handleFileUpload = async (files: File[]) => {
// 并行上传多个文件
const uploadPromises = files.map(file =>
toWithRetry(() => uploadFile(file), 3, 1000)
);
const results = await toAll(uploadPromises);
const successFiles = [];
const failedFiles = [];
results.forEach(([err, result], index) => {
if (err) {
failedFiles.push({ file: files[index], error: err.message });
} else {
successFiles.push(result);
}
});
if (successFiles.length > 0) {
ElMessage.success(`成功上传 ${successFiles.length} 个文件`);
}
if (failedFiles.length > 0) {
ElMessage.error(`${failedFiles.length} 个文件上传失败`);
console.error('上传失败的文件:', failedFiles);
}
};场景 3: 数据初始化流程
const initializeApp = async () => {
const initSteps = [
() => to(checkSystemHealth()),
() => to(loadApplicationConfig()),
() => to(initializeDatabase()),
() => to(setupEventListeners()),
() => to(loadUserPreferences())
];
const [err, results] = await toSequence(initSteps);
if (err) {
console.error('应用初始化失败:', err.message);
showErrorDialog('系统初始化失败,请刷新页面重试');
return;
}
console.log('应用初始化完成,执行了', results.length, '个步骤');
setAppReady(true);
};TypeScript 类型说明
Result 类型
type Result<T> = [Error | null, T | null];常用类型示例
// API 响应类型
interface ApiResponse<T> {
data: T;
message: string;
code: number;
}
// 使用示例
const [err, response] = await to<ApiResponse<User>>(fetchUser(id));
if (!err && response) {
const user: User = response.data;
}性能优化建议
1. 避免不必要的包装
// ❌ 不推荐:已经是 to 格式的函数重复包装
const result = await to(alreadyToWrappedFunction()); // 重复包装
// ✅ 推荐:使用 toResult 或直接调用
const result = await toResult(alreadyToWrappedFunction());
// 或者
const result = await alreadyToWrappedFunction();2. 合理使用重试机制
// ❌ 不推荐:对所有请求都使用重试
const [err, data] = await toWithRetry(() => fetchData(), 5, 1000);
// ✅ 推荐:只对不稳定的请求使用重试
const [err, data] = await toWithRetry(() => fetchUnstableData(), 2, 500);3. 批量请求性能
// ❌ 不推荐:串行请求
const user = await to(fetchUser());
const orders = await to(fetchOrders());
const preferences = await to(fetchPreferences());
// ✅ 推荐:并行请求
const [userResult, ordersResult, prefsResult] = await toAll([
fetchUser(),
fetchOrders(),
fetchPreferences()
]);总结
to.ts 工具类提供了完整的异步错误处理解决方案:
- 核心功能:
to()、toValidate()、toAll()覆盖 90% 的使用场景 - 增强功能:超时、重试、默认值等高级特性
- 开发友好:完整的 TypeScript 支持和清晰的 API 设计
- 性能优化:支持并行、串行、条件执行等多种模式
通过使用这些工具函数,可以显著提高代码的可读性、可维护性和健壮性,是现代前端开发中处理异步操作的最佳实践。
常见问题
1. to()函数返回值类型判断导致类型收窄失败
问题描述:
使用 to() 函数后,即使检查了 err 为 null,TypeScript 仍然认为 data 可能是 null:
// ❌ 类型收窄失败
const [err, user] = await to(fetchUser(id))
if (err) return
// TypeScript 报错: user 可能是 null
console.log(user.name) // Object is possibly 'null'问题原因:
- 元组类型的独立性 -
[Error | null, T | null]中两个元素在类型系统中是独立的 - TypeScript 无法推导关联性 - 编译器不知道
err === null时data一定不为null - to() 的返回类型定义 - 为了覆盖所有情况,返回类型必须包含
null
解决方案:
// ✅ 方案1: 使用非空断言(确信数据存在时)
const [err, user] = await to(fetchUser(id))
if (err) {
handleError(err)
return
}
console.log(user!.name) // 使用 ! 断言
// ✅ 方案2: 再次检查 data
const [err, user] = await to(fetchUser(id))
if (err || !user) {
handleError(err || new Error('用户数据为空'))
return
}
// 此时 user 类型自动收窄为非 null
console.log(user.name)// ✅ 方案3: 创建类型安全的包装函数
type SafeResult<T> =
| { success: true; data: T; error: null }
| { success: false; data: null; error: Error }
async function toSafe<T>(promise: Promise<T>): Promise<SafeResult<T>> {
try {
const data = await promise
return { success: true, data, error: null }
} catch (e) {
return {
success: false,
data: null,
error: e instanceof Error ? e : new Error(String(e))
}
}
}
// 使用时类型完全安全
const result = await toSafe(fetchUser(id))
if (!result.success) {
handleError(result.error)
return
}
// result.data 自动推导为 User 类型,非 null
console.log(result.data.name)// ✅ 方案4: 使用泛型约束确保返回值
async function toRequired<T>(promise: Promise<T>): Promise<[Error, null] | [null, T]> {
try {
const data = await promise
if (data === null || data === undefined) {
return [new Error('返回值为空'), null]
}
return [null, data]
} catch (e) {
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
// 使用
const [err, user] = await toRequired(fetchUser(id))
if (err) {
return handleError(err)
}
// user 类型收窄正确
console.log(user.name)// ✅ 方案5: 使用类型守卫函数
function hasData<T>(result: [Error | null, T | null]): result is [null, T] {
return result[0] === null && result[1] !== null
}
function hasError<T>(result: [Error | null, T | null]): result is [Error, null] {
return result[0] !== null
}
// 使用
const result = await to(fetchUser(id))
if (hasError(result)) {
const [error] = result
handleError(error) // error 类型是 Error
return
}
if (hasData(result)) {
const [, user] = result
console.log(user.name) // user 类型是 User
}2. toValidate()与不同UI框架的表单验证不兼容
问题描述:
toValidate() 专门针对 Element Plus 设计,与其他 UI 框架(如 Ant Design Vue、Naive UI、Vant)的表单验证 API 不兼容:
// ❌ 与 Ant Design Vue 不兼容
const [err, isValid] = await toValidate(antFormRef.value)
// 报错: antFormRef.value.validate 不是预期的格式
// ❌ 与 Naive UI 不兼容
const [err, isValid] = await toValidate(naiveFormRef.value)
// validate 返回 Promise<void> 或抛出错误问题原因:
- API 设计差异 - 不同 UI 框架的表单验证 API 差异很大
- 返回值格式不同 - 有的返回布尔值,有的抛出错误,有的返回验证结果对象
- 回调 vs Promise - 部分框架使用回调,部分使用 Promise
解决方案:
// ✅ 方案1: 创建适配不同框架的验证函数
// Element Plus 适配器
async function toValidateElementPlus(
formRef: Ref<FormInstance | undefined>
): Promise<[Error | null, boolean]> {
if (!formRef.value) {
return [new Error('表单引用不存在'), false]
}
try {
const valid = await formRef.value.validate()
return [null, valid]
} catch (e) {
return [e instanceof Error ? e : new Error('验证失败'), false]
}
}
// Ant Design Vue 适配器
async function toValidateAntd(
formRef: Ref<FormInstance | undefined>
): Promise<[Error | null, boolean]> {
if (!formRef.value) {
return [new Error('表单引用不存在'), false]
}
try {
await formRef.value.validate()
return [null, true]
} catch (e) {
// Ant Design Vue 验证失败会抛出 errorInfo
const errorInfo = e as { errorFields: Array<{ name: string[]; errors: string[] }> }
const firstError = errorInfo.errorFields?.[0]?.errors?.[0]
return [new Error(firstError || '验证失败'), false]
}
}
// Naive UI 适配器
async function toValidateNaive(
formRef: Ref<FormInst | undefined>
): Promise<[Error | null, boolean]> {
if (!formRef.value) {
return [new Error('表单引用不存在'), false]
}
try {
await formRef.value.validate()
return [null, true]
} catch (e) {
// Naive UI 验证失败会抛出错误数组
const errors = e as Array<{ message: string }>
const firstError = errors?.[0]?.message
return [new Error(firstError || '验证失败'), false]
}
}
// Vant 适配器
async function toValidateVant(
formRef: Ref<FormInstance | undefined>
): Promise<[Error | null, boolean]> {
if (!formRef.value) {
return [new Error('表单引用不存在'), false]
}
try {
await formRef.value.validate()
return [null, true]
} catch (e) {
// Vant 验证失败返回第一个错误信息
return [new Error(String(e) || '验证失败'), false]
}
}// ✅ 方案2: 创建统一的表单验证适配器工厂
type FormLibrary = 'element-plus' | 'antd' | 'naive-ui' | 'vant'
interface ValidateAdapter {
validate: (formRef: any) => Promise<[Error | null, boolean]>
}
function createValidateAdapter(library: FormLibrary): ValidateAdapter {
const adapters: Record<FormLibrary, ValidateAdapter> = {
'element-plus': {
async validate(formRef) {
if (!formRef?.value) return [new Error('表单引用不存在'), false]
try {
const valid = await formRef.value.validate()
return [null, valid !== false]
} catch (e) {
return [e instanceof Error ? e : new Error('验证失败'), false]
}
}
},
'antd': {
async validate(formRef) {
if (!formRef?.value) return [new Error('表单引用不存在'), false]
try {
await formRef.value.validate()
return [null, true]
} catch (e: any) {
const msg = e?.errorFields?.[0]?.errors?.[0] || '验证失败'
return [new Error(msg), false]
}
}
},
'naive-ui': {
async validate(formRef) {
if (!formRef?.value) return [new Error('表单引用不存在'), false]
try {
await formRef.value.validate()
return [null, true]
} catch (e: any) {
const msg = e?.[0]?.message || '验证失败'
return [new Error(msg), false]
}
}
},
'vant': {
async validate(formRef) {
if (!formRef?.value) return [new Error('表单引用不存在'), false]
try {
await formRef.value.validate()
return [null, true]
} catch (e) {
return [new Error(String(e) || '验证失败'), false]
}
}
}
}
return adapters[library]
}
// 使用
const validateAdapter = createValidateAdapter('antd')
const [err, isValid] = await validateAdapter.validate(formRef)// ✅ 方案3: 使用通用的验证包装器
async function toValidateUniversal<T>(
validateFn: () => Promise<T>,
successPredicate: (result: T) => boolean = () => true
): Promise<[Error | null, boolean]> {
try {
const result = await validateFn()
return [null, successPredicate(result)]
} catch (e) {
// 尝试从不同格式的错误中提取信息
let message = '验证失败'
if (e instanceof Error) {
message = e.message
} else if (typeof e === 'object' && e !== null) {
const errorObj = e as any
message =
errorObj.message ||
errorObj.errorFields?.[0]?.errors?.[0] ||
errorObj[0]?.message ||
String(e)
}
return [new Error(message), false]
}
}
// 使用 - 适用于任何框架
// Element Plus
const [err1, valid1] = await toValidateUniversal(
() => elementFormRef.value!.validate()
)
// Ant Design Vue
const [err2, valid2] = await toValidateUniversal(
() => antFormRef.value!.validate()
)
// Naive UI
const [err3, valid3] = await toValidateUniversal(
() => naiveFormRef.value!.validate()
)3. toWithRetry()重试策略配置不当导致请求堆积
问题描述:
不合理的重试配置会导致请求堆积、服务器压力增大、用户体验变差:
// ❌ 问题配置
const [err, data] = await toWithRetry(
() => fetch('/api/data'),
10, // 重试10次太多
100 // 间隔100ms太短
)
// 可能在短时间内发送10多次请求,造成服务器压力问题原因:
- 重试次数过多 - 对于已经不可用的服务,重试只是浪费资源
- 重试间隔过短 - 服务器可能需要恢复时间
- 缺少指数退避 - 固定间隔可能不适合所有场景
- 未区分错误类型 - 有些错误不应该重试(如 401、404)
解决方案:
// ✅ 方案1: 实现指数退避策略
interface RetryOptions {
maxRetries?: number
baseDelay?: number
maxDelay?: number
backoffFactor?: number
retryableErrors?: (error: Error) => boolean
onRetry?: (error: Error, attempt: number) => void
}
async function toWithExponentialBackoff<T>(
promiseFactory: () => Promise<T>,
options: RetryOptions = {}
): Promise<[Error | null, T | null]> {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 30000,
backoffFactor = 2,
retryableErrors = () => true,
onRetry
} = options
let lastError: Error | null = null
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await promiseFactory()
return [null, result]
} catch (e) {
lastError = e instanceof Error ? e : new Error(String(e))
// 检查是否应该重试
if (attempt >= maxRetries || !retryableErrors(lastError)) {
break
}
// 计算延迟时间 (指数退避 + 随机抖动)
const delay = Math.min(
baseDelay * Math.pow(backoffFactor, attempt) + Math.random() * 1000,
maxDelay
)
onRetry?.(lastError, attempt + 1)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
return [lastError, null]
}
// 使用示例
const [err, data] = await toWithExponentialBackoff(
() => fetch('/api/data').then(r => r.json()),
{
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2,
retryableErrors: (error) => {
// 只重试网络错误和 5xx 错误
if (error.message.includes('network')) return true
if (error.message.includes('500')) return true
if (error.message.includes('502')) return true
if (error.message.includes('503')) return true
return false
},
onRetry: (error, attempt) => {
console.log(`重试第 ${attempt} 次,原因: ${error.message}`)
}
}
)// ✅ 方案2: 根据HTTP状态码决定是否重试
interface HttpError extends Error {
status?: number
code?: string
}
function isRetryableHttpError(error: Error): boolean {
const httpError = error as HttpError
const status = httpError.status
// 不重试的情况
const nonRetryableStatuses = [
400, // Bad Request - 请求参数错误
401, // Unauthorized - 认证失败
403, // Forbidden - 权限不足
404, // Not Found - 资源不存在
422, // Unprocessable Entity - 业务逻辑错误
]
if (status && nonRetryableStatuses.includes(status)) {
return false
}
// 可重试的情况
const retryableStatuses = [
408, // Request Timeout
429, // Too Many Requests
500, // Internal Server Error
502, // Bad Gateway
503, // Service Unavailable
504, // Gateway Timeout
]
if (status && retryableStatuses.includes(status)) {
return true
}
// 网络错误可重试
if (error.message.includes('network') || error.message.includes('fetch')) {
return true
}
return false
}
// 使用
const [err, data] = await toWithExponentialBackoff(
() => apiRequest('/api/data'),
{
maxRetries: 3,
retryableErrors: isRetryableHttpError
}
)// ✅ 方案3: 带取消功能的重试
class RetryController {
private aborted = false
private abortReason: string | null = null
abort(reason = '用户取消') {
this.aborted = true
this.abortReason = reason
}
get isAborted() {
return this.aborted
}
get reason() {
return this.abortReason
}
reset() {
this.aborted = false
this.abortReason = null
}
}
async function toWithCancellableRetry<T>(
promiseFactory: () => Promise<T>,
controller: RetryController,
options: RetryOptions = {}
): Promise<[Error | null, T | null]> {
const { maxRetries = 3, baseDelay = 1000 } = options
let lastError: Error | null = null
for (let attempt = 0; attempt <= maxRetries; attempt++) {
// 检查是否已取消
if (controller.isAborted) {
return [new Error(controller.reason || '操作已取消'), null]
}
try {
const result = await promiseFactory()
return [null, result]
} catch (e) {
lastError = e instanceof Error ? e : new Error(String(e))
if (attempt < maxRetries && !controller.isAborted) {
const delay = baseDelay * Math.pow(2, attempt)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
return [lastError, null]
}
// 使用示例
const retryController = new RetryController()
// 开始请求
const requestPromise = toWithCancellableRetry(
() => fetch('/api/slow-data'),
retryController,
{ maxRetries: 5, baseDelay: 2000 }
)
// 用户点击取消按钮
cancelButton.onclick = () => {
retryController.abort('用户取消了请求')
}
const [err, data] = await requestPromise
if (err) {
if (err.message.includes('取消')) {
console.log('用户取消了操作')
} else {
console.error('请求失败:', err.message)
}
}4. toAll()部分失败时无法区分成功和失败的请求
问题描述:
使用 toAll() 并行请求时,难以追踪哪些请求成功、哪些失败:
// ❌ 难以追踪哪个请求失败
const results = await toAll([
fetchUser(userId),
fetchOrders(userId),
fetchPreferences(userId)
])
// results 是 [[err1, data1], [err2, data2], [err3, data3]]
// 需要手动对应每个结果解决方案:
// ✅ 方案1: 使用带标签的结果包装
interface TaggedResult<T> {
tag: string
error: Error | null
data: T | null
success: boolean
}
async function toAllTagged<T>(
requests: Array<{ tag: string; promise: Promise<T> }>
): Promise<TaggedResult<T>[]> {
const results = await Promise.all(
requests.map(async ({ tag, promise }) => {
try {
const data = await promise
return { tag, error: null, data, success: true }
} catch (e) {
return {
tag,
error: e instanceof Error ? e : new Error(String(e)),
data: null,
success: false
}
}
})
)
return results
}
// 使用
const results = await toAllTagged([
{ tag: 'user', promise: fetchUser(userId) },
{ tag: 'orders', promise: fetchOrders(userId) },
{ tag: 'preferences', promise: fetchPreferences(userId) }
])
// 轻松查找特定请求的结果
const userResult = results.find(r => r.tag === 'user')
if (userResult?.success) {
console.log('用户信息:', userResult.data)
}
// 统计成功和失败
const successCount = results.filter(r => r.success).length
const failedTags = results.filter(r => !r.success).map(r => r.tag)
console.log(`成功: ${successCount}, 失败: ${failedTags.join(', ')}`)// ✅ 方案2: 返回对象形式的结果
type PromiseMap<T> = Record<string, Promise<T>>
type ResultMap<T> = Record<string, { error: Error | null; data: T | null }>
async function toAllNamed<T extends PromiseMap<any>>(
promiseMap: T
): Promise<{ [K in keyof T]: { error: Error | null; data: Awaited<T[K]> | null } }> {
const keys = Object.keys(promiseMap)
const promises = Object.values(promiseMap)
const results = await Promise.all(
promises.map(async (promise) => {
try {
const data = await promise
return { error: null, data }
} catch (e) {
return {
error: e instanceof Error ? e : new Error(String(e)),
data: null
}
}
})
)
const resultMap: any = {}
keys.forEach((key, index) => {
resultMap[key] = results[index]
})
return resultMap
}
// 使用 - 类型安全,按名称访问结果
const results = await toAllNamed({
user: fetchUser(userId),
orders: fetchOrders(userId),
preferences: fetchPreferences(userId)
})
// 直接按名称访问
if (!results.user.error) {
console.log('用户:', results.user.data)
}
if (!results.orders.error) {
console.log('订单:', results.orders.data)
}// ✅ 方案3: 分离成功和失败的结果
interface PartitionedResults<T> {
succeeded: Array<{ index: number; data: T }>
failed: Array<{ index: number; error: Error }>
all: Array<{ index: number; success: boolean; data: T | null; error: Error | null }>
}
async function toAllPartitioned<T>(
promises: Promise<T>[]
): Promise<PartitionedResults<T>> {
const results = await Promise.all(
promises.map(async (promise, index) => {
try {
const data = await promise
return { index, success: true, data, error: null }
} catch (e) {
return {
index,
success: false,
data: null,
error: e instanceof Error ? e : new Error(String(e))
}
}
})
)
return {
succeeded: results
.filter(r => r.success)
.map(r => ({ index: r.index, data: r.data! })),
failed: results
.filter(r => !r.success)
.map(r => ({ index: r.index, error: r.error! })),
all: results
}
}
// 使用
const filePromises = files.map(file => uploadFile(file))
const { succeeded, failed, all } = await toAllPartitioned(filePromises)
console.log(`上传完成: ${succeeded.length} 成功, ${failed.length} 失败`)
// 处理成功的文件
succeeded.forEach(({ index, data }) => {
console.log(`文件 ${files[index].name} 上传成功:`, data.url)
})
// 处理失败的文件
failed.forEach(({ index, error }) => {
console.error(`文件 ${files[index].name} 上传失败:`, error.message)
})// ✅ 方案4: 带重试的批量请求
interface BatchRequestOptions {
concurrency?: number
retryFailed?: boolean
maxRetries?: number
}
async function toAllWithRetry<T>(
promiseFactories: Array<() => Promise<T>>,
options: BatchRequestOptions = {}
): Promise<{
results: Array<{ success: boolean; data: T | null; error: Error | null }>
retried: number
}> {
const { concurrency = 5, retryFailed = true, maxRetries = 2 } = options
const results: Array<{ success: boolean; data: T | null; error: Error | null }> = []
let retriedCount = 0
// 分批执行
for (let i = 0; i < promiseFactories.length; i += concurrency) {
const batch = promiseFactories.slice(i, i + concurrency)
const batchResults = await Promise.all(
batch.map(async (factory) => {
let lastError: Error | null = null
let retries = 0
while (retries <= maxRetries) {
try {
const data = await factory()
return { success: true, data, error: null }
} catch (e) {
lastError = e instanceof Error ? e : new Error(String(e))
if (retryFailed && retries < maxRetries) {
retriedCount++
retries++
await new Promise(r => setTimeout(r, 1000 * retries))
} else {
break
}
}
}
return { success: false, data: null, error: lastError }
})
)
results.push(...batchResults)
}
return { results, retried: retriedCount }
}
// 使用
const uploadFactories = files.map(file => () => uploadFile(file))
const { results, retried } = await toAllWithRetry(uploadFactories, {
concurrency: 3,
retryFailed: true,
maxRetries: 2
})
console.log(`共重试 ${retried} 次`)
const successCount = results.filter(r => r.success).length
console.log(`最终结果: ${successCount}/${files.length} 成功`)5. 嵌套使用to()导致代码冗余和错误处理重复
问题描述:
在复杂业务流程中,连续使用 to() 会产生大量重复的错误处理代码:
// ❌ 冗余的错误处理
const [userErr, user] = await to(fetchUser(id))
if (userErr) {
showError(userErr.message)
return
}
const [ordersErr, orders] = await to(fetchOrders(user.id))
if (ordersErr) {
showError(ordersErr.message)
return
}
const [prefsErr, prefs] = await to(fetchPreferences(user.id))
if (prefsErr) {
showError(prefsErr.message)
return
}
const [saveErr] = await to(saveUserData(user, orders, prefs))
if (saveErr) {
showError(saveErr.message)
return
}解决方案:
// ✅ 方案1: 创建链式执行器
class AsyncChain<T> {
private result: T | null = null
private error: Error | null = null
static start<U>(promise: Promise<U>): AsyncChain<U> {
return new AsyncChain<U>(promise)
}
private constructor(private promise: Promise<T>) {
this.init()
}
private async init() {
try {
this.result = await this.promise
} catch (e) {
this.error = e instanceof Error ? e : new Error(String(e))
}
}
async then<U>(fn: (data: T) => Promise<U>): Promise<AsyncChain<U>> {
await this.init()
if (this.error || !this.result) {
return AsyncChain.fromError(this.error || new Error('No data'))
}
return AsyncChain.start(fn(this.result))
}
async catch(handler: (error: Error) => void): Promise<T | null> {
await this.init()
if (this.error) {
handler(this.error)
}
return this.result
}
async unwrap(): Promise<[Error | null, T | null]> {
await this.init()
return [this.error, this.result]
}
static fromError<U>(error: Error): AsyncChain<U> {
const chain = new AsyncChain<U>(Promise.reject(error))
chain.error = error
return chain
}
}
// 使用 - 链式调用
const result = await AsyncChain
.start(fetchUser(id))
.then(user => fetchOrders(user.id))
.then(orders => processOrders(orders))
.catch(err => showError(err.message))// ✅ 方案2: 使用管道模式
type AsyncStep<I, O> = (input: I) => Promise<O>
async function toPipe<T>(
initialValue: T,
...steps: AsyncStep<any, any>[]
): Promise<[Error | null, any]> {
let currentValue: any = initialValue
for (const step of steps) {
try {
currentValue = await step(currentValue)
} catch (e) {
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
return [null, currentValue]
}
// 使用 - 管道执行
const [err, finalData] = await toPipe(
userId,
(id) => fetchUser(id),
(user) => fetchOrders(user.id),
(orders) => calculateTotalAmount(orders),
(total) => generateReport(total)
)
if (err) {
showError(err.message)
return
}
console.log('报告生成完成:', finalData)// ✅ 方案3: 创建统一的错误处理中间件
type ErrorHandler = (error: Error, context: string) => void
function createToWithContext(
defaultHandler: ErrorHandler
) {
return async function<T>(
promise: Promise<T>,
context: string,
customHandler?: ErrorHandler
): Promise<T | null> {
try {
return await promise
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e))
const handler = customHandler || defaultHandler
handler(error, context)
return null
}
}
}
// 创建带默认处理器的 to 函数
const toWithHandler = createToWithContext((error, context) => {
console.error(`[${context}] 错误:`, error.message)
ElMessage.error(`${context}失败: ${error.message}`)
})
// 使用 - 自动处理错误
const user = await toWithHandler(fetchUser(id), '获取用户信息')
if (!user) return
const orders = await toWithHandler(fetchOrders(user.id), '获取订单列表')
if (!orders) return
const result = await toWithHandler(processOrders(orders), '处理订单')
if (!result) return
console.log('流程完成:', result)// ✅ 方案4: 使用 Result 类实现
class Result<T, E = Error> {
private constructor(
private readonly _value: T | null,
private readonly _error: E | null
) {}
static ok<T>(value: T): Result<T, never> {
return new Result<T, never>(value, null)
}
static err<E>(error: E): Result<never, E> {
return new Result<never, E>(null, error)
}
static async from<T>(promise: Promise<T>): Promise<Result<T, Error>> {
try {
const value = await promise
return Result.ok(value)
} catch (e) {
return Result.err(e instanceof Error ? e : new Error(String(e)))
}
}
isOk(): boolean {
return this._error === null
}
isErr(): boolean {
return this._error !== null
}
unwrap(): T {
if (this._error) throw this._error
return this._value!
}
unwrapOr(defaultValue: T): T {
return this._error ? defaultValue : this._value!
}
map<U>(fn: (value: T) => U): Result<U, E> {
if (this._error) return Result.err(this._error)
return Result.ok(fn(this._value!))
}
async andThen<U>(fn: (value: T) => Promise<Result<U, Error>>): Promise<Result<U, Error | E>> {
if (this._error) return Result.err(this._error)
return fn(this._value!)
}
match<U>(handlers: { ok: (value: T) => U; err: (error: E) => U }): U {
return this._error ? handlers.err(this._error) : handlers.ok(this._value!)
}
}
// 使用
const userResult = await Result.from(fetchUser(id))
const finalResult = await userResult
.andThen(user => Result.from(fetchOrders(user.id)))
.then(result => result.andThen(orders => Result.from(processOrders(orders))))
finalResult.match({
ok: (data) => console.log('成功:', data),
err: (error) => console.error('失败:', error.message)
})6. 异步操作组件卸载后继续执行导致内存泄漏
问题描述:
组件卸载后,to() 包装的异步操作仍在执行,导致内存泄漏和错误:
// ❌ 组件卸载后异步操作仍会执行
const loadData = async () => {
const [err, data] = await to(fetchHeavyData())
if (err) return
// 组件已卸载,但这里仍会执行
state.value = data // 警告: 更新已卸载组件的状态
showNotification('数据加载完成') // 可能报错
}
onMounted(() => {
loadData()
})解决方案:
// ✅ 方案1: 使用 AbortController 取消请求
import { onMounted, onUnmounted, ref } from 'vue'
function useAbortableRequest() {
const abortController = ref<AbortController | null>(null)
const execute = async <T>(
requestFn: (signal: AbortSignal) => Promise<T>
): Promise<[Error | null, T | null]> => {
// 取消之前的请求
abortController.value?.abort()
// 创建新的控制器
abortController.value = new AbortController()
const signal = abortController.value.signal
try {
const data = await requestFn(signal)
return [null, data]
} catch (e) {
if (e instanceof Error && e.name === 'AbortError') {
return [new Error('请求已取消'), null]
}
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
const abort = () => {
abortController.value?.abort()
abortController.value = null
}
onUnmounted(() => {
abort()
})
return { execute, abort }
}
// 在组件中使用
const { execute, abort } = useAbortableRequest()
const loadData = async () => {
const [err, data] = await execute((signal) =>
fetch('/api/heavy-data', { signal }).then(r => r.json())
)
if (err) {
if (err.message === '请求已取消') return // 组件卸载,静默处理
showError(err.message)
return
}
state.value = data
}// ✅ 方案2: 创建可取消的 to 函数
import { onUnmounted, shallowRef } from 'vue'
interface CancellablePromise<T> {
promise: Promise<T>
cancel: () => void
}
function makeCancellable<T>(promise: Promise<T>): CancellablePromise<T> {
let isCancelled = false
const wrappedPromise = new Promise<T>((resolve, reject) => {
promise.then(
value => !isCancelled && resolve(value),
error => !isCancelled && reject(error)
)
})
return {
promise: wrappedPromise,
cancel: () => { isCancelled = true }
}
}
function useCancellableTo() {
const pending = shallowRef<Array<() => void>>([])
const toWithCancel = async <T>(
promise: Promise<T>
): Promise<[Error | null, T | null, boolean]> => {
const { promise: cancellablePromise, cancel } = makeCancellable(promise)
pending.value.push(cancel)
try {
const data = await cancellablePromise
return [null, data, false]
} catch (e) {
return [e instanceof Error ? e : new Error(String(e)), null, false]
}
}
const cancelAll = () => {
pending.value.forEach(cancel => cancel())
pending.value = []
}
onUnmounted(() => {
cancelAll()
})
return { to: toWithCancel, cancelAll }
}
// 使用
const { to: safeTo } = useCancellableTo()
const loadData = async () => {
const [err, data] = await safeTo(fetchHeavyData())
// 如果组件已卸载,这里不会执行
if (!err && data) {
state.value = data
}
}// ✅ 方案3: 使用 VueUse 的 useAsyncState
import { useAsyncState } from '@vueuse/core'
// 在组件中
const { state, isLoading, error, execute } = useAsyncState(
() => fetchHeavyData(),
null, // 初始值
{
immediate: true,
resetOnExecute: true,
onError: (e) => {
console.error('加载失败:', e)
}
}
)
// useAsyncState 会自动处理组件卸载的情况// ✅ 方案4: 封装带生命周期感知的 to 函数
import { getCurrentInstance, onUnmounted } from 'vue'
function useLifecycleAwareTo() {
const instance = getCurrentInstance()
let isUnmounted = false
onUnmounted(() => {
isUnmounted = true
})
return async function<T>(
promise: Promise<T>
): Promise<[Error | null, T | null, boolean]> {
const [err, data] = await to(promise)
// 返回第三个参数表示组件是否已卸载
return [err, data, isUnmounted]
}
}
// 使用
const toSafe = useLifecycleAwareTo()
const loadData = async () => {
const [err, data, isUnmounted] = await toSafe(fetchHeavyData())
// 组件已卸载,停止后续操作
if (isUnmounted) return
if (err) {
showError(err.message)
return
}
state.value = data
}7. toWithTimeout()超时后原始Promise仍在执行消耗资源
问题描述:
toWithTimeout() 超时后只是返回错误,但原始 Promise 仍在后台执行:
// ❌ 超时后原始请求仍在执行
const [err, data] = await toWithTimeout(
fetch('/api/heavy-computation'), // 这个请求仍在后台执行
5000
)
if (err) {
console.log('请求超时')
// 但实际上请求可能还在执行,浪费带宽和服务器资源
}解决方案:
// ✅ 方案1: 使用 AbortController 实现真正的超时取消
async function toWithAbortableTimeout<T>(
requestFn: (signal: AbortSignal) => Promise<T>,
timeoutMs: number,
timeoutMessage = '请求超时'
): Promise<[Error | null, T | null]> {
const controller = new AbortController()
const { signal } = controller
const timeoutId = setTimeout(() => {
controller.abort()
}, timeoutMs)
try {
const result = await requestFn(signal)
clearTimeout(timeoutId)
return [null, result]
} catch (e) {
clearTimeout(timeoutId)
if (e instanceof Error && e.name === 'AbortError') {
return [new Error(timeoutMessage), null]
}
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
// 使用
const [err, data] = await toWithAbortableTimeout(
(signal) => fetch('/api/heavy-computation', { signal }).then(r => r.json()),
5000,
'计算超时,请稍后重试'
)// ✅ 方案2: 封装支持取消的请求函数
interface CancellableRequest<T> {
execute: () => Promise<[Error | null, T | null]>
cancel: (reason?: string) => void
}
function createCancellableRequest<T>(
requestFn: (signal: AbortSignal) => Promise<T>,
options: {
timeout?: number
timeoutMessage?: string
} = {}
): CancellableRequest<T> {
const controller = new AbortController()
const { signal } = controller
let timeoutId: NodeJS.Timeout | null = null
const execute = async (): Promise<[Error | null, T | null]> => {
if (options.timeout) {
timeoutId = setTimeout(() => {
controller.abort()
}, options.timeout)
}
try {
const result = await requestFn(signal)
if (timeoutId) clearTimeout(timeoutId)
return [null, result]
} catch (e) {
if (timeoutId) clearTimeout(timeoutId)
if (e instanceof Error && e.name === 'AbortError') {
return [new Error(options.timeoutMessage || '请求被取消'), null]
}
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
const cancel = (reason = '用户取消') => {
if (timeoutId) clearTimeout(timeoutId)
controller.abort()
}
return { execute, cancel }
}
// 使用
const request = createCancellableRequest(
(signal) => fetch('/api/data', { signal }).then(r => r.json()),
{ timeout: 5000, timeoutMessage: '请求超时' }
)
// 开始请求
const [err, data] = await request.execute()
// 或者在某个时刻取消
// request.cancel('用户点击了取消按钮')// ✅ 方案3: 使用 Promise.race 配合资源清理
async function toWithTimeoutAndCleanup<T>(
promise: Promise<T>,
timeoutMs: number,
cleanup?: () => void,
timeoutMessage = '操作超时'
): Promise<[Error | null, T | null]> {
let timeoutId: NodeJS.Timeout
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => {
cleanup?.() // 执行清理操作
reject(new Error(timeoutMessage))
}, timeoutMs)
})
try {
const result = await Promise.race([promise, timeoutPromise])
clearTimeout(timeoutId!)
return [null, result]
} catch (e) {
clearTimeout(timeoutId!)
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
// 使用 - WebSocket 连接超时
const ws = new WebSocket('wss://example.com')
const [err, connection] = await toWithTimeoutAndCleanup(
new Promise<WebSocket>((resolve, reject) => {
ws.onopen = () => resolve(ws)
ws.onerror = (e) => reject(e)
}),
5000,
() => ws.close(), // 超时时关闭连接
'连接超时'
)// ✅ 方案4: 创建超时管理器
class TimeoutManager {
private activeRequests = new Map<string, AbortController>()
createRequest<T>(
id: string,
requestFn: (signal: AbortSignal) => Promise<T>,
timeoutMs: number
): {
execute: () => Promise<[Error | null, T | null]>
cancel: () => void
} {
// 取消之前的同ID请求
this.cancel(id)
const controller = new AbortController()
this.activeRequests.set(id, controller)
const execute = async (): Promise<[Error | null, T | null]> => {
const timeoutId = setTimeout(() => {
this.cancel(id)
}, timeoutMs)
try {
const result = await requestFn(controller.signal)
clearTimeout(timeoutId)
this.activeRequests.delete(id)
return [null, result]
} catch (e) {
clearTimeout(timeoutId)
this.activeRequests.delete(id)
if (e instanceof Error && e.name === 'AbortError') {
return [new Error('请求超时或被取消'), null]
}
return [e instanceof Error ? e : new Error(String(e)), null]
}
}
return {
execute,
cancel: () => this.cancel(id)
}
}
cancel(id: string) {
const controller = this.activeRequests.get(id)
if (controller) {
controller.abort()
this.activeRequests.delete(id)
}
}
cancelAll() {
this.activeRequests.forEach(controller => controller.abort())
this.activeRequests.clear()
}
get activeCount() {
return this.activeRequests.size
}
}
// 全局实例
const timeoutManager = new TimeoutManager()
// 使用
const searchRequest = timeoutManager.createRequest(
'user-search',
(signal) => fetch(`/api/users?q=${query}`, { signal }).then(r => r.json()),
3000
)
const [err, users] = await searchRequest.execute()
// 用户快速输入时,自动取消之前的搜索请求8. 复杂业务流程中to()的错误类型丢失和上下文信息缺失
问题描述:
在复杂业务流程中,难以追踪错误发生的具体位置和上下文:
// ❌ 错误信息丢失上下文
const [err1, user] = await to(fetchUser(id))
if (err1) return handleError(err1) // 不知道是哪个步骤失败
const [err2, orders] = await to(fetchOrders(user.id))
if (err2) return handleError(err2) // 错误信息可能很模糊
const [err3, result] = await to(processOrders(orders))
if (err3) return handleError(err3) // 难以定位问题解决方案:
// ✅ 方案1: 创建带上下文的错误类型
class ContextualError extends Error {
constructor(
message: string,
public readonly context: string,
public readonly step: string,
public readonly originalError?: Error,
public readonly metadata?: Record<string, any>
) {
super(`[${context}:${step}] ${message}`)
this.name = 'ContextualError'
}
toJSON() {
return {
name: this.name,
message: this.message,
context: this.context,
step: this.step,
metadata: this.metadata,
stack: this.stack
}
}
}
async function toWithContext<T>(
promise: Promise<T>,
context: string,
step: string,
metadata?: Record<string, any>
): Promise<[ContextualError | null, T | null]> {
try {
const data = await promise
return [null, data]
} catch (e) {
const originalError = e instanceof Error ? e : new Error(String(e))
const contextualError = new ContextualError(
originalError.message,
context,
step,
originalError,
metadata
)
return [contextualError, null]
}
}
// 使用
const [err, user] = await toWithContext(
fetchUser(id),
'用户管理',
'获取用户信息',
{ userId: id }
)
if (err) {
console.error('错误上下文:', err.context)
console.error('失败步骤:', err.step)
console.error('元数据:', err.metadata)
console.error('原始错误:', err.originalError)
return
}// ✅ 方案2: 创建业务流程追踪器
interface FlowStep<T> {
name: string
execute: () => Promise<T>
onError?: (error: Error) => void
onSuccess?: (data: T) => void
}
interface FlowResult<T> {
success: boolean
data: T | null
error: Error | null
failedStep: string | null
completedSteps: string[]
duration: number
}
async function executeFlow<T>(
flowName: string,
steps: FlowStep<any>[],
initialData?: any
): Promise<FlowResult<T>> {
const startTime = Date.now()
const completedSteps: string[] = []
let currentData: any = initialData
for (const step of steps) {
try {
console.log(`[${flowName}] 执行步骤: ${step.name}`)
currentData = await step.execute()
completedSteps.push(step.name)
step.onSuccess?.(currentData)
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e))
step.onError?.(error)
return {
success: false,
data: null,
error: new Error(`[${flowName}] 步骤 "${step.name}" 失败: ${error.message}`),
failedStep: step.name,
completedSteps,
duration: Date.now() - startTime
}
}
}
return {
success: true,
data: currentData,
error: null,
failedStep: null,
completedSteps,
duration: Date.now() - startTime
}
}
// 使用
const result = await executeFlow<ProcessedOrder[]>('订单处理流程', [
{
name: '获取用户信息',
execute: () => fetchUser(userId),
onError: (e) => console.error('获取用户失败:', e)
},
{
name: '获取订单列表',
execute: async () => {
const user = await fetchUser(userId)
return fetchOrders(user.id)
},
onSuccess: (orders) => console.log(`获取到 ${orders.length} 个订单`)
},
{
name: '处理订单数据',
execute: () => processOrders(orders)
},
{
name: '保存处理结果',
execute: () => saveResults(processedOrders)
}
])
if (!result.success) {
console.error('流程失败:', result.error?.message)
console.log('已完成步骤:', result.completedSteps.join(' → '))
console.log('失败步骤:', result.failedStep)
console.log('耗时:', result.duration, 'ms')
// 可以根据失败步骤进行回滚
if (result.failedStep === '保存处理结果') {
await rollbackProcessing()
}
}// ✅ 方案3: 创建错误边界和恢复机制
interface ErrorBoundary<T> {
try: <U>(fn: () => Promise<U>, stepName: string) => ErrorBoundary<U>
catch: (handler: (error: Error, step: string) => T | Promise<T>) => Promise<T>
finally: (callback: () => void) => ErrorBoundary<T>
}
function createErrorBoundary<T>(): ErrorBoundary<T> {
let chain: Promise<any> = Promise.resolve()
let catchHandler: ((error: Error, step: string) => T | Promise<T>) | null = null
let finallyCallback: (() => void) | null = null
let failedStep: string | null = null
const boundary: ErrorBoundary<T> = {
try<U>(fn: () => Promise<U>, stepName: string): ErrorBoundary<U> {
chain = chain.then(async () => {
try {
return await fn()
} catch (e) {
failedStep = stepName
throw e
}
})
return boundary as unknown as ErrorBoundary<U>
},
catch(handler: (error: Error, step: string) => T | Promise<T>): Promise<T> {
catchHandler = handler
return chain.catch(async (e) => {
const error = e instanceof Error ? e : new Error(String(e))
return catchHandler!(error, failedStep || 'unknown')
}).finally(() => {
finallyCallback?.()
})
},
finally(callback: () => void): ErrorBoundary<T> {
finallyCallback = callback
return boundary
}
}
return boundary
}
// 使用
const result = await createErrorBoundary<null>()
.try(() => fetchUser(userId), '获取用户')
.try((user) => fetchOrders(user.id), '获取订单')
.try((orders) => processOrders(orders), '处理订单')
.try((processed) => saveResults(processed), '保存结果')
.finally(() => {
console.log('流程结束')
})
.catch((error, step) => {
console.error(`步骤 "${step}" 失败:`, error.message)
// 根据失败步骤采取不同的恢复策略
switch (step) {
case '获取用户':
showError('用户信息获取失败,请重试')
break
case '获取订单':
showError('订单数据加载失败')
break
case '处理订单':
// 可以返回部分结果
return partialResults
case '保存结果':
// 保存失败,提供重试选项
showRetryDialog()
break
}
return null
})// ✅ 方案4: 集成日志和监控
interface TrackedOperation<T> {
id: string
name: string
startTime: number
endTime?: number
status: 'pending' | 'success' | 'error'
error?: Error
result?: T
parentId?: string
}
class OperationTracker {
private operations: Map<string, TrackedOperation<any>> = new Map()
private currentParentId: string | null = null
startOperation(name: string): string {
const id = `${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`
this.operations.set(id, {
id,
name,
startTime: Date.now(),
status: 'pending',
parentId: this.currentParentId || undefined
})
return id
}
completeOperation<T>(id: string, result: T) {
const op = this.operations.get(id)
if (op) {
op.endTime = Date.now()
op.status = 'success'
op.result = result
}
}
failOperation(id: string, error: Error) {
const op = this.operations.get(id)
if (op) {
op.endTime = Date.now()
op.status = 'error'
op.error = error
}
}
async track<T>(name: string, fn: () => Promise<T>): Promise<[Error | null, T | null]> {
const id = this.startOperation(name)
const previousParent = this.currentParentId
this.currentParentId = id
try {
const result = await fn()
this.completeOperation(id, result)
return [null, result]
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e))
this.failOperation(id, error)
return [error, null]
} finally {
this.currentParentId = previousParent
}
}
getReport(): {
total: number
success: number
failed: number
pending: number
avgDuration: number
failedOperations: TrackedOperation<any>[]
} {
const ops = Array.from(this.operations.values())
const completed = ops.filter(o => o.endTime)
const durations = completed.map(o => o.endTime! - o.startTime)
return {
total: ops.length,
success: ops.filter(o => o.status === 'success').length,
failed: ops.filter(o => o.status === 'error').length,
pending: ops.filter(o => o.status === 'pending').length,
avgDuration: durations.length > 0
? durations.reduce((a, b) => a + b, 0) / durations.length
: 0,
failedOperations: ops.filter(o => o.status === 'error')
}
}
clear() {
this.operations.clear()
}
}
// 全局追踪器
const tracker = new OperationTracker()
// 使用
const [err1, user] = await tracker.track('获取用户', () => fetchUser(id))
if (err1) return
const [err2, orders] = await tracker.track('获取订单', () => fetchOrders(user.id))
if (err2) return
const [err3, result] = await tracker.track('处理订单', () => processOrders(orders))
if (err3) {
// 查看完整报告
const report = tracker.getReport()
console.log('操作报告:', report)
console.log('失败的操作:', report.failedOperations)
return
}