本地存储与并发锁工具
本节介绍前端框架提供的两类基础设施:
LocalStorage包装类:用于安全地读写本地存储- 一组并发锁工具:
ReentrantLock、ReadWriteLock、StampedLock、CountDownLatch、CyclicBarrier等
这些工具在复杂前端场景下(多请求并发、Web Worker 协作、跨组件共享状态)能显著简化代码。
1. 本地存储封装:LocalStorage
框架对浏览器的 localStorage 做了一层简单的类型安全封装,避免:
- 重复的
JSON.stringify/parse代码 - key 命名分散、难以统一管理
具体实现可在
projects/framework/src/lib/local.storage.ts中查看,这里只给出使用示例。
import {LocalStorage} from '@rydeen/angular-framework';
const storage = new LocalStorage('APP_'); // 可选前缀
storage.set('token', 'xxx');
const token = storage.get<string>('token');
storage.setObject('profile', { name: 'Alice', age: 18 });
const profile = storage.getObject<{ name: string; age: number }>('profile');
建议在应用中再封装一层「领域化」的存储服务,例如 AuthStorageService、SettingsStorageService,统一管理 key 与结构。
2. 并发锁工具
在传统前端开发中,我们较少显式使用「锁」的概念,但在以下场景中却非常有用:
- 避免同一段初始化逻辑被多次并发执行(如数据库 schema 加载)
- 多个异步任务需要等待同一批操作完成后再继续(如多接口聚合)
- 在 Web Worker / Service 层控制对某些资源的串行访问
框架提供的锁工具 API 设计上大量借鉴了 Java java.util.concurrent 包中的类,便于后端开发者迁移思维模式。
2.1 ReentrantLock(可重入锁)
import {ReentrantLock} from '@rydeen/angular-framework';
const lock = new ReentrantLock();
async function criticalSection() {
await lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
生活化类比
- 想象你家厨房只有一把煤气灶,做饭时需要「轮流进厨房」:
- 谁先拿到门口的钥匙,谁就先进去做菜;
- 做完以后把钥匙放回去,下一个人才能进去。
- 这把「厨房钥匙」就是一把最简单的互斥锁(
ReentrantLock),保证同一时间只有一个人操作煤气灶,避免混乱和危险。
适用的前端业务场景
- 数据库迁移 / 本地初始化:确保迁移脚本、IndexedDB 初始化只会有一个任务在跑,避免并发执行导致重复写入或锁死。
- 全局缓存刷新:在同一时间只允许一个请求去刷新大缓存,其它请求直接复用结果或等待,避免服务端被瞬间打爆。
- 关键配置更新:比如 App 主题、语言、全局设置的变更,保证同一时间不会有两个并发更新流程互相覆盖。
带来的提升
- 从「用各种 flag/if 控制时序」变成「显式声明这段代码‘同一时间只能跑一次’」,业务意图更清晰,AI 和人都更容易读懂与重构。
2.2 ReadWriteLock(读写锁)
在读多写少的场景中,可以用读写锁提升并发度:
import {ReadWriteLock} from '@rydeen/angular-framework';
const lock = new ReadWriteLock();
async function readConfig() {
await lock.readLock();
try {
// 允许多个读取同时进行
} finally {
lock.readUnlock();
}
}
async function updateConfig() {
await lock.writeLock();
try {
// 写操作期间会阻塞所有其它读写
} finally {
lock.writeUnlock();
}
}
生活化类比
- 图书馆的阅览室:
- 读者很多,大家可以同时看同一本书(读锁,共享);
- 但当管理员要「更新/修复」这本书时,必须先把书收回来,这时所有读者都暂时不能看(写锁,独占)。
- 读写锁就是把这种规则用代码表达出来:多数情况下大家都可以并发读取,只有在修改时才会短暂阻塞所有读写。
适用的前端业务场景
- 全局配置/字典数据:例如从服务端拉取的业务配置(菜单配置、权限配置、字典项等),大部分时间只是读,偶尔刷新。
- 本地缓存视图:多个组件同时读取同一份缓存(如用户信息、主题配置)时,刷新缓存的逻辑可以使用写锁保护,避免在更新过程中读到一半。
带来的提升
- 比全局互斥锁更「温柔」:
- 大量读请求之间不会互相阻塞;
- 只有真正需要写时,才会短暂停止其它访问。
- 在高并发页面(多组件依赖同一份配置)下,可以同时保证正确性与响应速度。
2.3 CountDownLatch / CyclicBarrier
当你需要「等待 N 个异步任务全部完成」时,可以使用 CountDownLatch:
import {CountDownLatch} from '@rydeen/angular-framework';
async function loadAll() {
const latch = new CountDownLatch(3);
this.loadMenu().finally(() => latch.countDown());
this.loadUsers().finally(() => latch.countDown());
this.loadSettings().finally(() => latch.countDown());
await latch.await(); // 等待三个任务全部完成
// 继续后续流程
}
CyclicBarrier 则适合「阶段性汇合点」的场景,例如多 Worker 在每一轮计算结束后统一对齐状态。
生活化类比
CountDownLatch:像「集齐三个人就开饭」——- 你约了 3 个朋友吃火锅,只有当三个人都到达后才正式开锅;每来一个就在清单上打勾,直到勾满。
CyclicBarrier:像「游戏副本里的下一阶段倒计时」——- 多个玩家分别在不同区域打怪,当所有人都完成当前阶段后,系统才一起进入下一阶段,这个过程会重复多轮。
适用的前端业务场景
CountDownLatch:- 多接口聚合首页:首页需要菜单、用户信息、配置三类数据,全部准备好后再一次性展示,避免闪烁与多次布局抖动。
- 预加载资源:等待几类图片/配置/缓存全部初始化后再进入主界面。
CyclicBarrier:- 多 Web Worker 协作计算:比如在离线模式下做本地大数据统计,每一轮统计需要多个 Worker 各自处理一部分数据,全部完成后统一合并结果,再开始下一轮。
带来的提升
- 让「等所有人都准备好再开始」这种业务规则有了显式表达,而不是到处散落
Promise.all、回调地狱或一堆状态 flag。 - 对 AI 和后续维护者来说,一看到
CountDownLatch/CyclicBarrier,就能快速理解这是在做「聚合点/阶段同步」,便于自动生成和重构。
3. 场景示例:数据库迁移与锁
在 cross-platform-app 中,数据库迁移与查询构建较为复杂,通常需要保证:
- 迁移脚本只执行一次
- 不同模块在访问数据库时遵循某种顺序或隔离策略
可以使用锁工具将这些约束显式地表达出来,而不是依赖「约定」:
// 伪代码示例
export class MigrationService {
private readonly lock = new ReentrantLock();
async migrateIfNeeded() {
await this.lock.lock();
try {
const done = await this.checkMigrationDone();
if (!done) {
await this.runMigrations();
}
} finally {
this.lock.unlock();
}
}
}
4. 小结
与传统「完全依赖 Promise / Observable,不显式使用锁」的写法相比:
- 意图更清晰:看到
lock/latch就能立即明白这是在控制并发,而不是一堆杂乱的if/flag。 - 迁移成本低:后端同学可以几乎原样搬运 Java 里常见并发模式到前端。
- 更易维护:对于复杂场景,显式锁要比隐式的「时序假设」更容易排查问题与重构。