蓝牙抽象层与基础组件
本文档介绍 JL OTA Harmony 工程中 ets/bluetooth/base 目录下的蓝牙抽象层与基础组件:设备抽象基类 BluetoothDevice、数据发送处理器抽象类 BaseSendDataHandler、异步缓冲队列 BufferQueue、统一错误码 BluetoothErrorConstant,以及扫描/连接契约 IScan、IConnect 在整体蓝牙架构中的定位与使用方式。
Purpose and Scope
本页聚焦于蓝牙模块的基础层(base layer),即所有传输栈(BLE、SPP)共享的抽象契约、基类与基础设施组件:
- 设备模型抽象:
BluetoothDevice抽象类与BluetoothDeviceType类型联合 - 发送链路的抽象契约:
BaseSendDataHandler抽象类 - 异步数据缓冲:
BufferQueue<T>通用队列 - 统一错误码体系:
BluetoothErrorConstant枚举 - 扫描/连接契约:
IScan、IConnect接口文件在基础层的定位
以下内容不在本页范围内,由兄弟页面分别承载:
- BLE 传输栈的具体实现(
BleImpl、BleDevice、BleSendDataHandler、IBleConnect、IBleScan及扫描/连接配置)→ 见 BLE 层页面 - SPP(经典蓝牙串口)传输栈的具体实现(
SppImpl、SppDevice等)→ 见 SPP 层页面 - 模块级门面与 OTA 业务流程编排(
BluetoothManager、BluetoothOTAManager)→ 见蓝牙管理页面
本页只描述基础层自身的结构、行为与设计意图;传输栈如何继承/实现这些契约,以各自页面的源码为准。
Overview
JL OTA 的蓝牙模块采用分层 + 契约解耦的架构。OTA 业务层(BluetoothOTAManager)不直接依赖具体的蓝牙传输协议,而是通过模块门面(BluetoothManager)与基础层定义的一组抽象契约交互;基础层则把"设备长什么样"(BluetoothDevice)、"数据怎么发"(BaseSendDataHandler)、"数据怎么缓冲"(BufferQueue)、"出错怎么表示"(BluetoothErrorConstant)统一抽象出来,由 BLE 与 SPP 两个传输栈分别落地。
这种设计的目的(WHY):
- 传输协议可替换:同一套 OTA 协议逻辑可以跑在 BLE 或 SPP 之上,切换传输栈不触碰业务代码。
- 统一错误语义:所有传输层错误都归一为
BluetoothErrorConstant中的数值,上层只需判断一个枚举,而不必关心底层异常类型。 - 异步解耦:数据发送与接收方通过
BufferQueue的 Promise 化接口解耦,发送方不必轮询、接收方不必忙等。
基础层组件全部位于 entry/src/main/ets/bluetooth/base/ 目录,共 6 个文件:
| 文件 | 角色 |
|---|---|
BluetoothDevice.ets | 设备抽象基类 + 设备类型联合 |
BaseSendDataHandler.ets | 发送数据处理器抽象基类(契约壳) |
BufferQueue.ets | 通用异步 FIFO 缓冲队列 |
BluetoothErrorConstant.ets | 蓝牙错误码枚举 |
IScan.ets | 扫描操作抽象契约 |
IConnect.ets | 连接操作抽象契约 |
架构
flowchart TD
subgraph sg_App["应用 / OTA 业务层"]
OTA["BluetoothOTAManager"]
BT["BluetoothManager"]
end
subgraph sg_Base["基础层(本页主题)"]
DEV["BluetoothDevice(抽象类)"]
SEND["BaseSendDataHandler(抽象类)"]
QUEUE["BufferQueue<T>"]
ERR["BluetoothErrorConstant"]
ISCAN["IScan(契约)"]
ICONN["IConnect(契约)"]
end
subgraph sg_BLE["BLE 传输栈(另页)"]
BLE["BleImpl"]
BLEDEV["BleDevice"]
BLESEND["BleSendDataHandler"]
end
subgraph sg_SPP["SPP 传输栈(另页)"]
SPP["SppImpl"]
SPPDEV["SppDevice"]
end
OTA --> BT
BT --> ISCAN
BT --> ICONN
ISCAN --> BLE
ISCAN --> SPP
ICONN --> BLE
ICONN --> SPP
DEV -. "被具体设备类继承" .-> BLEDEV
DEV -. "被具体设备类继承" .-> SPPDEV
SEND -. "被具体发送处理器继承" .-> BLESEND
QUEUE --> SEND
ERR -. "统一错误码引用" .-> BLE
ERR -. "统一错误码引用" .-> SPP
架构说明:
- 基础层是本页主题,处于中间位置:向上提供业务层可用的抽象(设备、发送、错误、队列),向下定义传输栈必须实现的契约(
IScan、IConnect)。 - BLE 传输栈与 SPP 传输栈是基础层契约的两个落地实现,分别位于
bluetooth/ble与bluetooth/spp目录;虚线箭头表示"继承/实现"关系,其具体细节以 BLE、SPP 各自页面的源码为准(本页未展开读取这两个目录的实文件)。 BufferQueue<T>服务于发送链路:发送处理器(如BleSendDataHandler)借助队列在异步回调与上层发送请求之间做缓冲,避免数据丢失与回调重入。BluetoothErrorConstant是全模块共享的错误语义:BLE 与 SPP 实现都把底层错误翻译为该枚举值,保证上层错误处理路径唯一。
说明:
IScan/IConnect与具体实现(BleImpl/SppImpl)之间的精确绑定关系(是否通过IBleScan/ISppScan等中间接口继承)属于传输栈页面的内容,本页不臆测其细节。
基础组件深度分析
设备抽象:BluetoothDevice
BluetoothDevice 是蓝牙模块对"一台可连接设备"的最小抽象,也是 BLE 与 SPP 两类设备模型的公共基类。其完整定义如下:
export type BluetoothDeviceType = "EDR" | "BLE"
export abstract class BluetoothDevice {
deviceId: string;
isSystemConnected = false
type: BluetoothDeviceType = "BLE"
deviceName?: string
connectable: boolean = true
rssi?:number
constructor(deviceId: string) {
this.deviceId = deviceId
}
}
Source: BluetoothDevice.ets
设计要点:
deviceId是唯一标识:构造器强制要求传入,扫描结果与连接操作都以它为键。这也是错误码ERROR_INVALID_DATA(deviceId 为空或格式不正确)的校验对象。type区分传输族:BluetoothDeviceType = "EDR" | "BLE"覆盖经典蓝牙(EDR/SPP)与低功耗蓝牙(BLE)两条链路;默认值是"BLE",即未显式指定时按 BLE 处理——这反映了该 SDK 以 BLE 为主链路的默认取向。isSystemConnected记录系统级连接态:初始为false,由各传输栈在系统连接/断开回调中维护,与上层"逻辑连接"状态解耦。connectable/rssi/deviceName是扫描结果的通用属性:rssi与deviceName为可选字段(?),因为某些扫描场景(如只按 MAC 过滤)拿不到名称或信号强度。
抽象类本身不可实例化,具体设备模型(BLE 栈的 BleDevice、SPP 栈的 SppDevice)继承它并补充各自传输特有的字段(服务 UUID、特征、波特率等),详见对应传输栈页面。
发送链路契约:BaseSendDataHandler
export abstract class BaseSendDataHandler {
}
Source: BaseSendDataHandler.ets
该抽象类目前是一个空契约壳:文件中没有任何成员声明。它的作用在于为发送数据处理器建立类型边界——各传输栈的发送处理器(如 BLE 栈的 BleSendDataHandler)继承它,从而让上层可以面向 BaseSendDataHandler 编程而不依赖具体传输协议。当前版本的具体发送逻辑(分片、流控、队列消费)实现在各传输栈的派生类中,本文件只负责抽象定位。
注:由于本文件没有方法签名,具体发送接口(参数、返回值、异常)必须以派生类源码为准,见 BLE/SPP 层页面。
异步缓冲队列:BufferQueue<T>
BufferQueue<T> 是基础层最具分量的运行时组件:一个支持异步等待出队的通用 FIFO 队列,用于在数据发送路径上衔接"生产方(上层调用)"与"消费方(底层回调/发送循环)"。
export class BufferQueue<T> {
private items: T[] = [];
private dequeuePromise?: Promise<T | undefined>;
private resolveDequeue?: (value: T | undefined) => void;
enqueue(item: T): void {
this.items.push(item);
// 如果有等待的dequeue操作,则解决它
if (this.resolveDequeue) {
this.resolveDequeue(this.dequeue());
this.resetDequeuePromise();
}
}
dequeue(): T | undefined {
return this.items.shift();
}
getItemList() {
return this.items
}
setItemList(items: T[]) {
this.items = items
}
async dequeueAsync(): Promise<T | undefined> {
// 如果队列为空,则创建一个新的Promise并等待
if (this.isEmpty()) {
if (!this.dequeuePromise) {
this.dequeuePromise = new Promise(resolve => {
this.resolveDequeue = resolve;
});
}
return this.dequeuePromise;
}
// 如果队列不为空,则直接出队并返回结果
return Promise.resolve(this.dequeue());
}
clear() {
this.items = [];
// 如果队列被清空且有等待的dequeue操作,则拒绝它(可选)
if (this.resolveDequeue) {
this.resolveDequeue(undefined);
this.resetDequeuePromise();
}
}
isEmpty(): boolean {
return this.items.length === 0;
}
private resetDequeuePromise() {
this.dequeuePromise = undefined;
this.resolveDequeue = undefined;
}
}
Source: BufferQueue.ets
工作机制(逐行解读):
- 内部状态:
items保存数据;dequeuePromise保存"当前正在等待的 Promise";resolveDequeue保存该 Promise 的 resolve 函数。后两者构成一个单等待者的异步门闩。 enqueue(item):先push入队;随后检查是否存在等待者(resolveDequeue非空)。若存在,立即调用resolveDequeue(this.dequeue())—— 把刚入队的元素shift出来直接交付给等待者,并resetDequeuePromise()复位门闩。也就是说,当队列为空且有消费者在等待时,入队与出队被融合为一次操作,数据零延迟到达消费者。dequeueAsync():若队列为空,创建(或复用)一个 Promise 并返回它,调用方await挂起;若队列非空,则同步shift一个元素并返回已解决的 Promise。这使消费方可以写成自然的while (true) { const item = await queue.dequeueAsync(); ... }循环,无需轮询。clear():清空数据并把等待者以undefined唤醒(注释标注"拒绝它(可选)",实际实现是 resolve 而非 reject),随后复位门闩,防止悬挂 Promise。getItemList()/setItemList(items):提供对内部数组的直接访问/替换能力,供上层在特殊场景(如批量回灌、调试)使用。
关键并发语义:BufferQueue 依赖 ArkTS 单线程事件循环模型,enqueue 与 dequeueAsync 之间不需要锁;但它同一时刻只支持一个等待者(dequeuePromise 是单值)。若多个消费者同时 await dequeueAsync() 且队列为空,后到者拿到的仍是同一个 Promise,只有一个会被唤醒——因此该队列面向"单生产者/单消费者"或"多生产者/单消费者"场景,不适用于多消费者竞争取数。
统一错误码:BluetoothErrorConstant
export enum BluetoothErrorConstant {
//蓝牙错误
ERROR_NONE = 0, //ok | 正常 |adapter
ERROR_CONNECTED = -1, // already connect | 已连接 |
ERROR_ADAPTER_NOT_INIT = 10000, // not init | 未初始化蓝牙适配器 |
ERROR_ADAPTER_NOT_AVAILABLE = 10001, // not available | 当前蓝牙适配器不可用 |
ERROR_NO_DEV = 10002, // no device | 没有找到指定设备 |
ERROR_CONNECTION_FAIL = 10003, // connection fail | 连接失败 |
ERROR_NO_SERVICE = 10004, // no service | 没有找到指定服务 |
ERROR_NO_CHARACTERISTIC = 10005, // no characteristic | 没有找到指定特征 |
ERROR_NO_CONNECTION = 10006, // no connection | 当前连接已断开 |
ERROR_PROPERTY_NOT_SUPPORT = 10007, // property not support | 当前特征不支持此操作 |
ERROR_SYSTEM_ERROR = 10008, // system error | 其余所有系统上报的异常 |
ERROR_SYSTEM_NOT_SUPPORT = 10009, // system not support | Android 系统特有,系统版本低于 4.3 不支持 BLE |
ERROR_OPERATE_TIME_OUT = 10012, // operate time out | 连接超时 |
ERROR_INVALID_DATA = 10013, // invalid_data | 连接 deviceId 为空或者是格式不正确 |
ERROR_INIT_MTU_FAIL = 20000, // init mtu fail | 初始化MTU失败 |
ERROR_GET_SERVICE_FAIL = 20001, // get service fail | 获取服务失败 |
ERROR_NOTIFY_NECESSARY_CHARACTERISTIC_FAIL = 20002, // notify necessary characteristic fail | 使能必须的特征失败 |
ERROR_IS_CONNECTING = 20003, // is connecting | 正在连接 |
}
Source: BluetoothErrorConstant.ets
错误码按语义分四段,设计意图清晰:
- 0 / -1:特殊状态。
ERROR_NONE表示成功;ERROR_CONNECTED用负值表示"已连接",属于状态提示而非失败。 - 10000–10013:通用蓝牙错误。覆盖适配器状态(未初始化/不可用)、设备查找、连接失败/超时、服务与特征查找、连接断开、属性不支持、系统异常、系统不支持、非法数据。这些错误对 BLE 与 SPP 两条链路通用。
- 20000–20003:连接初始化链路错误,主要服务于 BLE 的 GATT 连接流程:MTU 协商失败、获取服务失败、使能必要特征失败、正在连接(防止重入)。
上层统一用该枚举判断结果:例如值为 ERROR_NONE 即成功,ERROR_OPERATE_TIME_OUT 触发重试策略,ERROR_IS_CONNECTING 提示调用方避免重复发起连接。错误码的数值稳定性是其作为跨层契约的关键——任何传输栈实现都必须原样透传这些数值,不得自行改写。
契约接口:IScan 与 IConnect
IScan.ets 与 IConnect.ets 定义了蓝牙操作的两个高层契约:扫描(发现设备)与连接(建立会话)。它们位于基础层,表示"任何传输栈都必须具备这两类能力";具体方法签名由各传输栈的派生接口(BLE 侧的 IBleScan/IBleConnect、SPP 侧的 ISppScan/ISppConnect,见 bluetooth/ble 与 bluetooth/spp 目录)细化并实现。
由于本页未读取这两个文件的方法体,此处不臆测其签名;它们的角色定位(扫描/连接抽象)与实现绑定方式,请以 BLE、SPP 传输栈页面的源码为准。
核心流程
数据缓冲的异步消费流程
BufferQueue 是本页唯一有运行时行为的组件,其核心交互——"空队列等待 + 入队即唤醒"——是理解发送链路的关键:
sequenceDiagram
participant Sender as 生产者(上层发送调用)
participant Q as BufferQueue<T>
participant Receiver as 消费者(发送循环/回调)
Receiver->>Q: dequeueAsync()(队列为空)
Note over Q: 创建 dequeuePromise 并挂起
Sender->>Q: enqueue(item)
Note over Q: resolveDequeue(dequeue())<br/>将刚入队的元素直接交付
Q-->>Receiver: 返回 item(Promise 解决)
Receiver->>Receiver: 处理/发送该数据块
Receiver->>Q: dequeueAsync()(再次等待下一块)
Source: BufferQueue.ets
流程要点:
- 消费者先
await dequeueAsync(),因队列为空而挂起,内部保存resolveDequeue。 - 生产者
enqueue(item):push后立即发现等待者,执行resolveDequeue(this.dequeue()),把元素交付。 - 消费者被唤醒拿到元素,处理完毕后再次
dequeueAsync(),进入下一轮等待——形成无轮询的生产者-消费者循环。 - 若队列非空时调用
dequeueAsync(),则直接shift返回,无需挂起。
错误码的归一化路径
flowchart LR
Err["底层异常/系统回调"] --> Trans["BleImpl / SppImpl<br/>错误翻译"]
Trans --> Code["BluetoothErrorConstant 枚举值"]
Code --> Mgr["BluetoothManager / 业务层判断"]
Mgr -->|"ERROR_NONE"| Ok["继续流程"]
Mgr -->|"ERROR_OPERATE_TIME_OUT"| Retry["重试策略"]
Mgr -->|"ERROR_IS_CONNECTING"| Guard["防重入"]
所有传输栈实现把系统层异常(连接失败、服务/特征缺失、超时等)翻译为 BluetoothErrorConstant 中的统一数值,业务层只依赖该枚举做分支判断,不感知底层 API 差异。
使用示例
示例一:定义具体设备类并继承抽象基类
以下代码展示 BluetoothDevice 作为抽象基类的用法——具体设备类通过 extends 复用公共字段,并补充传输特有字段(示例结构参照 BLE/SPP 设备类的模式):
export abstract class BluetoothDevice {
deviceId: string;
isSystemConnected = false
type: BluetoothDeviceType = "BLE"
deviceName?: string
connectable: boolean = true
rssi?:number
constructor(deviceId: string) {
this.deviceId = deviceId
}
}
Source: BluetoothDevice.ets
实际工程中,BleDevice(BLE 栈)与 SppDevice(SPP 栈)继承本类并各自持有传输特有的配置字段;它们的完整定义见对应传输栈页面。
示例二:异步消费 BufferQueue
dequeueAsync() 允许消费方写出"等待-取数-处理"的自然循环,无需定时轮询:
async dequeueAsync(): Promise<T | undefined> {
// 如果队列为空,则创建一个新的Promise并等待
if (this.isEmpty()) {
if (!this.dequeuePromise) {
this.dequeuePromise = new Promise(resolve => {
this.resolveDequeue = resolve;
});
}
return this.dequeuePromise;
}
// 如果队列不为空,则直接出队并返回结果
return Promise.resolve(this.dequeue());
}
Source: BufferQueue.ets
典型消费循环(示意):
const queue = new BufferQueue<Uint8Array>();
// 消费方:持续取数据块并发送
while (true) {
const chunk = await queue.dequeueAsync();
if (chunk === undefined) break; // clear() 后返回 undefined,循环退出
sendToDevice(chunk);
}
注意 clear() 会把等待者以 undefined 唤醒,因此消费者需要用 undefined 作为"队列已清空/停止"的退出信号。
示例三:错误码判断
export enum BluetoothErrorConstant {
ERROR_NONE = 0, //ok | 正常 |adapter
ERROR_CONNECTED = -1, // already connect | 已连接 |
ERROR_OPERATE_TIME_OUT = 10012, // operate time out | 连接超时 |
ERROR_IS_CONNECTING = 20003, // is connecting | 正在连接 |
}
Source: BluetoothErrorConstant.ets
业务层通过数值比较即可完成分支:=== ERROR_NONE 判成功、=== ERROR_OPERATE_TIME_OUT 触发重试、=== ERROR_IS_CONNECTING 拒绝重复连接。
配置选项
BluetoothDevice 的字段即基础层的"设备配置面",由扫描结果或调用方赋值:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
deviceId | string | 构造器必填 | 设备唯一标识(MAC/地址),连接与错误校验的依据 |
type | BluetoothDeviceType | "BLE" | 传输族:"EDR"(经典蓝牙/SPP)或 "BLE" |
isSystemConnected | boolean | false | 系统级连接状态,由传输栈回调维护 |
deviceName | string? | 无 | 设备名称(扫描可选信息) |
connectable | boolean | true | 设备是否可连接 |
rssi | number? | 无 | 信号强度(扫描可选信息) |
API Reference
BufferQueue<T> 类
泛型异步 FIFO 队列,单等待者语义。
方法:
| 方法签名 | 说明 | 返回 |
|---|---|---|
enqueue(item: T): void | 入队;若有等待者则立即交付并复位门闩 | 无 |
dequeue(): T | undefined | 同步出队(shift),空队列返回 undefined | T | undefined |
dequeueAsync(): Promise<T | undefined> | 异步出队;空队列时挂起等待 enqueue 唤醒 | Promise<T | undefined> |
clear(): void | 清空数据,以 undefined 唤醒等待者并复位门闩 | 无 |
isEmpty(): boolean | 是否为空 | boolean |
getItemList(): T[] | 返回内部数组引用 | T[] |
setItemList(items: T[]): void | 整体替换内部数组 | 无 |
Throws:无显式异常;clear() 使用 resolve(而非 reject)唤醒等待者。
BluetoothDevice 抽象类
构造器:constructor(deviceId: string) —— 必须传入设备 ID,空值/格式错误对应 ERROR_INVALID_DATA。
字段:见上方配置表。
Throws:无(抽象类不可实例化)。
BaseSendDataHandler 抽象类
空契约壳,无成员;派生类提供具体发送实现。
BluetoothErrorConstant 枚举
完整取值与语义见上文代码块;数值分段规则:0/-1 状态值、10000–10013 通用错误、20000–20003 连接初始化错误。
失败模式、边界情况与并发
BufferQueue 的边界语义
- 空队列 + 多消费者竞争:
dequeuePromise/resolveDequeue是单值门闩,同一时刻仅支持一个等待者。若多个消费者同时await dequeueAsync()且队列为空,它们拿到的是同一个 Promise,只有首个enqueue会唤醒其中一个;其余消费者需要再次调用才能保证取到数据。因此该队列不适合"多消费者竞争取数"场景,设计上假定消费方唯一(如单条发送循环)。 enqueue与等待者的融合出队:enqueue在存在等待者时执行resolveDequeue(this.dequeue()),等价于"入队即出队"——被唤醒的消费者拿到的正是刚入队的元素,不会造成数据错位;但若调用方在enqueue后仍期望元素留在队列中(例如还准备用getItemList()读取),需注意该元素可能已被交付。clear()的唤醒约定:clear()以undefinedresolve 等待者并复位门闩,而不是 reject。消费者必须以undefined作为"队列已清空/停止"的退出信号(参见上文消费循环示例),否则会因拿到undefined而误判为正常数据。shift()的数组开销:dequeue()使用Array.prototype.shift(),对长队列是 O(n) 移动;OTA 发送场景单块数据通常较小,可接受,但不建议用本队列承载超大数据量的高频吞吐。- 直接暴露内部数组:
getItemList()返回内部items的引用,外部修改会直接影响队列状态;setItemList()可整体替换。这两个方法突破了封装边界,应仅在调试/特殊回灌场景使用。
错误码相关边界
ERROR_CONNECTED = -1是状态提示而非错误,上层逻辑需把它与真正的失败(正数错误码)区分处理。ERROR_SYSTEM_NOT_SUPPORT注释标明为"Android 系统特有",在 HarmonyOS 侧该值仍被保留以维持数值契约稳定,跨平台移植时不应改动枚举数值。- 连接初始化链路错误(20000–20003)集中在 BLE 的 GATT 流程:MTU 协商、服务发现、特征使能任一环节失败都会以独立错误码暴露,便于上层定位是"设备能力不足"还是"时序问题"。
并发模型
基础层全部组件运行在 ArkTS 单线程事件循环之上:BufferQueue 的 Promise 门闩无需互斥锁;BluetoothDevice 的字段读写也不存在多线程竞争。真正的并发风险来自异步回调重入——例如连接回调与上层重试同时修改 isSystemConnected/连接状态,需要依赖 ERROR_IS_CONNECTING 这类防重入错误码在上层做串行化。
性能与运维
- 零轮询等待:
dequeueAsync()的 Promise 门闩设计使消费方在无数据时完全不消耗 CPU,这是相对轮询方案的关键优势。 - 入队延迟为 O(1)(有等待者时):入队+出队融合为一次
shift,数据交付延迟最小。 - 错误码数值契约:枚举值在模块间作为协议传输,任何版本迭代都不应重排数值;新增错误码应追加新成员而非复用已有值。
- 基础层无网络/文件 IO,运行期开销集中在
BufferQueue的数组操作与 Promise 创建上;大量短促enqueue/dequeueAsync会频繁创建/销毁 Promise,若有极致性能诉求可考虑对象池复用。
扩展点
基础层的抽象设计为传输栈扩展提供了明确的接缝:
- 新增设备模型:继承
BluetoothDevice,补充传输特有字段(BLE 的 UUID/MTU、SPP 的波特率/通道等),现有模式参照BleDevice/SppDevice。 - 新增发送处理器:继承
BaseSendDataHandler,实现具体分片与发送逻辑,现有实现BleSendDataHandler即挂接在 BLE 栈发送链路上。 - 实现扫描/连接契约:
IScan/IConnect定义了能力边界,新增传输栈(如未来的双模合并栈)只需实现这两个契约并接入BluetoothManager,上层 OTA 流程无需改动。 - 复用统一错误码:新传输栈的所有失败路径都应翻译为
BluetoothErrorConstant数值,保持上层错误处理单一化。
测试覆盖
本页所读取的基础层源码中未包含单元测试文件;BufferQueue 的异步门闩行为(空队列挂起、入队唤醒、clear 以 undefined 退出)是值得补充测试的关键路径,可参照其源码注释中的语义编写用例(等待者存在/不存在、多次 dequeueAsync、clear 后继续 enqueue 等分支)。