错误处理模式Skill error-handling

此技能教授如何使用wellcrafted库的trySync和tryAsync函数进行优雅的错误处理,替代传统try-catch块。适用于处理Result类型和实现错误恢复,强调类型安全和显式错误管理。关键词:错误处理、trySync、tryAsync、Result类型、TypeScript、优雅恢复。

架构设计 0 次安装 0 次浏览 更新于 3/20/2026

name: 错误处理 description: 使用wellcrafted的trySync和tryAsync的错误处理模式。当编写错误处理代码、使用try-catch块或处理Result类型和优雅错误恢复时使用。 metadata: author: epicenter version: ‘1.1’

使用wellcrafted的trySync和tryAsync进行错误处理

使用trySync/tryAsync替代try-catch进行优雅错误处理

当处理可以优雅恢复的错误时,使用wellcrafted的trySync(用于同步代码)或tryAsync(用于异步代码)替代传统的try-catch块。这提供了更好的类型安全性和显式错误处理。

相关技能:参见services-layer技能了解createTaggedError模式。参见query-layer技能了解错误转换为WhisperingError

模式

import { trySync, tryAsync, Ok, Err } from 'wellcrafted/result';

// 同步:使用trySync进行同步操作
const { data, error } = trySync({
	try: () => {
		const parsed = JSON.parse(jsonString);
		return validateData(parsed); // 自动包装在Ok()中
	},
	catch: (e) => {
		// 优雅处理解析/验证错误
		console.log('使用默认配置');
		return Ok(defaultConfig); // 返回Ok并带有回退
	},
});

// 异步:使用tryAsync进行异步操作
await tryAsync({
	try: async () => {
		const child = new Child(session.pid);
		await child.kill();
		console.log(`进程成功杀死`);
	},
	catch: (e) => {
		// 优雅处理错误
		console.log(`进程已经终止`);
		return Ok(undefined); // 对于void函数返回Ok(undefined)
	},
});

// 两者都支持相同的catch模式
const syncResult = trySync({
	try: () => riskyOperation(),
	catch: (error) => {
		// 对于可恢复错误,返回带有回退值的Ok
		return Ok('fallback-value');
		// 对于不可恢复错误,返回Err
		return ServiceErr({
			message: '操作失败',
			cause: error,
		});
	},
});

关键规则

  1. 选择正确的函数 - 同步代码使用trySync,异步代码使用tryAsync
  2. 总是await tryAsync - 与try-catch不同,tryAsync返回Promise,必须被await
  3. trySync立即返回 - 同步操作不需要await
  4. 匹配返回类型 - 如果try块返回T,catch应返回Ok<T>进行优雅处理
  5. 使用Ok(undefined)表示void - 当函数返回void时,在catch中使用Ok(undefined)
  6. 返回Err进行传播 - 使用返回Err的自定义错误构造函数,当您想要传播错误时
  7. 关键:使用Err()包装解构的错误 - 当您从tryAsync/trySync解构{ data, error }时,error变量是原始错误值,不是包装在Err中的。您必须在返回前包装它:
// 错误 - error只是原始的TaggedError,不是Result
const { data, error } = await tryAsync({...});
if (error) return error; // 类型错误:返回TaggedError,不是Result

// 正确 - 使用Err()包装以返回正确的Result
const { data, error } = await tryAsync({...});
if (error) return Err(error); // 返回Err<TaggedError>

这与返回整个结果对象不同:

// 这也正确 - userResult已经是Result类型
const userResult = await tryAsync({...});
if (userResult.error) return userResult; // 返回完整的Result

示例

// 同步:JSON解析与回退
const { data: config } = trySync({
	try: () => JSON.parse(configString),
	catch: (e) => {
		console.log('无效配置,使用默认值');
		return Ok({ theme: 'dark', autoSave: true });
	},
});

// 同步:文件系统检查
const { data: exists } = trySync({
	try: () => fs.existsSync(path),
	catch: () => Ok(false), // 假设检查失败时不存在
});

// 异步:优雅进程终止
await tryAsync({
	try: async () => {
		await process.kill();
	},
	catch: (e) => {
		console.log('进程已经死亡,继续...');
		return Ok(undefined);
	},
});

// 异步:文件操作与回退
const { data: content } = await tryAsync({
	try: () => readFile(path),
	catch: (e) => {
		console.log('文件未找到,使用默认内容');
		return Ok('默认内容');
	},
});

// 两者:错误传播(两者都适用)
const { data, error } = await tryAsync({
	try: () => criticalOperation(),
	catch: (error) =>
		ServiceErr({
			message: '关键操作失败',
			cause: error,
		}),
});
if (error) return Err(error);

何时使用trySync、tryAsync和try-catch

  • 使用trySync时

    • 处理同步操作(JSON解析、验证、计算)
    • 需要立即的Result类型而无需promises
    • 在同步实用函数中处理错误
    • 处理文件系统同步操作
  • 使用tryAsync时

    • 处理async/await操作
    • 发起网络请求或数据库调用
    • 异步读写文件
    • 任何返回Promise的操作
  • 使用传统的try-catch时

    • 在模块级初始化代码中,不能使用await时
    • 用于简单的即发即忘操作
    • 当您处于函数上下文之外时
    • 当与期望抛出异常的代码集成时

包装模式:最小化与扩展化

最小化包装原则

只包装可能失败的特定操作。 这样可以精确捕获错误边界,使代码更易于理解。

// ✅ 好:只包装有风险的操作
const { data: stream, error: streamError } = await tryAsync({
	try: () => navigator.mediaDevices.getUserMedia({ audio: true }),
	catch: (error) =>
		DeviceStreamServiceErr({
			message: `麦克风访问失败:${extractErrorMessage(error)}`,
		}),
});

if (streamError) return Err(streamError);

// 继续执行非抛出操作
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
// ❌ 坏:包装过多代码
const { data, error } = await tryAsync({
	try: async () => {
		const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
		const mediaRecorder = new MediaRecorder(stream);
		mediaRecorder.start();
		await someOtherAsyncCall();
		return processResults();
	},
	catch: (error) => GenericErr({ message: '某些操作失败' }), // 太模糊了!
});

立即返回模式

检查后立即返回错误。 这样可以创建清晰的控制流,防止错误嵌套。

// ✅ 好:立即检查并返回
const { data: devices, error: enumerateError } = await enumerateDevices();
if (enumerateError) return Err(enumerateError);

const { data: stream, error: streamError } = await getStreamForDevice(
	devices[0],
);
if (streamError) return Err(streamError);

// 顺利继续快乐路径
return Ok(stream);
// ❌ 坏:嵌套错误处理
const { data: devices, error: enumerateError } = await enumerateDevices();
if (!enumerateError) {
	const { data: stream, error: streamError } = await getStreamForDevice(
		devices[0],
	);
	if (!streamError) {
		return Ok(stream);
	} else {
		return Err(streamError);
	}
} else {
	return Err(enumerateError);
}

何时扩展try块

有时将多个操作放在一个try块中是合理的:

  1. 原子操作 - 当操作必须一起成功或失败时
  2. 相同错误类型 - 当所有操作产生相同错误类别时
  3. 清理逻辑 - 当任何失败时都需要清理时
// 扩展块在此处合适 - 所有操作都是“开始录制”的一部分
const { data: mediaRecorder, error: recorderError } = trySync({
	try: () => {
		const recorder = new MediaRecorder(stream, { bitsPerSecond: bitrate });
		recorder.addEventListener('dataavailable', handleData);
		recorder.start(TIMESLICE_MS);
		return recorder;
	},
	catch: (error) =>
		RecorderServiceErr({
			message: `初始化录制器失败:${extractErrorMessage(error)}`,
		}),
});

来自代码库的真实示例

最小化包装并立即返回:

// 来自device-stream.ts
async function getStreamForDeviceIdentifier(
	deviceIdentifier: DeviceIdentifier,
) {
	return tryAsync({
		try: async () => {
			const stream = await navigator.mediaDevices.getUserMedia({
				audio: { ...constraints, deviceId: { exact: deviceIdentifier } },
			});
			return stream;
		},
		catch: (error) =>
			DeviceStreamServiceErr({
				message: `无法连接到麦克风。${extractErrorMessage(error)}`,
			}),
	});
}

多个最小化包装并立即返回:

// 来自navigator.ts
startRecording: async (params, { sendStatus }) => {
  if (activeRecording) {
    return RecorderServiceErr({ message: '已经在录制。' });
  }

  // 第一个try块 - 获取流
  const { data: streamResult, error: acquireStreamError } =
    await getRecordingStream({ selectedDeviceId, sendStatus });
  if (acquireStreamError) return Err(acquireStreamError);

  const { stream, deviceOutcome } = streamResult;

  // 第二个try块 - 创建录制器
  const { data: mediaRecorder, error: recorderError } = trySync({
    try: () => new MediaRecorder(stream, { bitsPerSecond: bitrate }),
    catch: (error) => RecorderServiceErr({
      message: `初始化录制器失败。${extractErrorMessage(error)}`,
    }),
  });

  if (recorderError) {
    cleanupRecordingStream(stream);  // 失败时清理
    return Err(recorderError);
  }

  // 顺利继续快乐路径...
  mediaRecorder.start(TIMESLICE_MS);
  return Ok(deviceOutcome);
},

总结:包装指南

场景 方法
单一有风险操作 只包装该操作
顺序操作 分别包装每个,错误时立即返回
必须一起成功的原子操作 一起包装在一个块中
需要不同错误类型 用适当错误类型分开块
失败时需要清理 包装,检查错误,如果需要清理,然后返回

目标:每个trySync/tryAsync块应表示一个单一的“失败单元”,带有具体、描述性的错误消息。