HarmonyOS 7 小艺动作执行到一半失败:怎样补偿已经完成的步骤
·

HarmonyOS 7 小艺动作执行到一半失败:怎样补偿已经完成的步骤
一个意图先创建草稿,再加入索引。第二步失败时,第一步已经生效,界面却只提示失败,下一次重试又多出一份草稿。这类多步骤操作需要补偿,而不是把所有错误吞掉后重试。
适配范围
失败步骤可能在抛错前已产生部分副作用,示例只撤销成功返回的步骤。每个run必须明确原子性或具备查询回执的办法;补偿过程要持久化、幂等,并保留人工处理入口。
案例一
索引步骤失败:创建步骤的返回id交给撤销函数,按逆序补偿。
案例二
撤销也失败:把原失败和撤销失败同时保留,交给恢复队列,不能假装已经回到操作前。
可以运行的实现
export async function compensated(steps) {
const done = [];
try {
for(const step of steps){const value=await step.run();done.push({step,value});}
return done.map(x=>x.value);
} catch(error) {
const failures=[];
for(const item of done.reverse())try{await item.step.undo(item.value);}catch(e){failures.push(String(e));}
throw new AggregateError([error,...failures],'action_failed');
}
}
复现与断言
把实现与以下测试放进同一个example.mjs,执行node example.mjs。断言失败会报错,全部通过正常退出。这些断言已经在本地Node.js执行;它们不代表SDK编译、真机接口调用或性能实测已经通过。
import assert from 'node:assert/strict';
const events=[];
await assert.rejects(compensated([{run:async()=>{events.push('create');return 'id';},undo:async id=>events.push('remove:'+id)},{run:async()=>{throw Error('index');},undo:async()=>{}}]));
assert.deepEqual(events,['create','remove:id']);
const ok=await compensated([{run:async()=>7,undo:async()=>{throw Error('not_needed');}}]);assert.deepEqual(ok,[7]);
let undid=false;
await assert.rejects(compensated([{run:async()=>1,undo:async()=>{undid=true;throw Error('undo');}},{run:async()=>{throw Error('run');},undo:async()=>{}}]),e=>e.errors.length===2);
assert.equal(undid,true);
方案取舍
本地数据库事务能覆盖的操作优先用事务;跨服务或跨资源无法放在同一事务时才考虑补偿。补偿不是精确回滚,发消息或发送邮件这样的副作用甚至无法撤销。
接入时别漏掉这些边界
失败步骤可能在抛错前已产生部分副作用,示例只撤销成功返回的步骤。每个run必须明确原子性或具备查询回执的办法;补偿过程要持久化、幂等,并保留人工处理入口。
复用时把输入获取与纯逻辑分开:界面、系统事件和服务请求负责取得真实输入,独立模块负责给出判断或保存状态。保留上述两个案例作为回归测试,并对失败原因记录最少必要日志,不记录文件内容、账号凭据或其它敏感数据。升级SDK后重新核对支持设备、接口版本与错误行为,而不是只改目标版本号。
官方资料
示例验证环境为Node.js,验证范围是应用侧逻辑。本文不将示例当作系统接口接入代码;具体接口的支持设备、最低版本和调用方式,以所用SDK对应的官方文档为准。
更多推荐
所有评论(0)