JavaScript中无法用Promise实现真正的流式处理,因其为一次性终态异步容器;语音识别需结合事件监听与Promise封装(如单次识别)或AsyncIterator(如实时interim响应)来兼顾简洁性与流式能力。

JavaScript 中无法直接用 Promise 实现真正的“流式处理”,因为 Promise 本质上是一次性、终态(fulfilled/rejected)的异步容器,不支持持续 emit 多个值。语音识别(如 Web Speech API 的 SpeechRecognition)天然具有流式特性——它会分多次触发 result 事件,每次返回部分识别结果(含 interim 结果)。要兼顾 Promise 的简洁性和流式响应能力,需结合事件监听 + Promise 封装策略,常见做法是:用 Promise 包裹单次完整识别流程(如首次稳定结果或超时终止),或用 AsyncIterator(配合 for await...of)实现真正可迭代的异步流。
用 Promise 封装一次完整的识别会话
适合“听一句、等结果、再继续”的场景。核心是监听 result 和 end 事件,手动控制 Promise 的 resolve/reject 时机:
- 监听
result:当event.results[i].isFinal === true时,提取最终文本并 resolve - 监听
end:若识别结束但未收到 final 结果,可 reject 或 resolve 空字符串(按需) - 设置超时:避免用户沉默导致 Promise 永远不结束,用
setTimeout主动 reject
示例代码:
function startSpeechRecognition() {
return new Promise((resolve, reject) => {
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.interimResults = true;
recognition.maxAlternatives = 1;
<pre class="brush:php;toolbar:false;">let timeoutId = setTimeout(() => {
recognition.stop();
reject(new Error('Recognition timeout'));
}, 10000);
recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
clearTimeout(timeoutId);
resolve(transcript);
return;
}
}
};
recognition.onerror = (e) => {
clearTimeout(timeoutId);
reject(e.error);
};
recognition.onend = () => {
if (timeoutId) {
clearTimeout(timeoutId);
// 可选:无 final 结果时 resolve 空字符串或上一个 interim 值
resolve('');
}
};
recognition.start();
}); }
// 使用 startSpeechRecognition() .then(text => console.log('Final result:', text)) .catch(err => console.error('Error:', err));
用 AsyncIterator 实现真正的流式消费
若需实时响应每个 interim 结果(如语音输入框逐字上屏),Promise 不够用,应返回一个异步迭代器。借助 AsyncGenerator 将事件转为 yield 流:
立即学习“Java免费学习笔记(深入)”;
- 创建
async function*,内部用addEventListener监听result - 每次收到
result事件,检查isFinal或直接 yield 所有结果(含 interim) - 调用方用
for await...of持续接收,手动控制何时break或return
示例:
async function* speechStream() {
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.interimResults = true;
recognition.continuous = false; // 单次识别,避免无限监听
<p>const controller = new AbortController();</p><p>recognition.onresult = (event) => {
for (let i = 0; i < event.results.length; i++) {
yield {
text: event.results[i][0].transcript,
isFinal: event.results[i].isFinal,
confidence: event.results[i][0].confidence
};
}
};</p><p>recognition.onerror = (e) => controller.abort(e.error);
recognition.onend = () => controller.abort();</p><p>recognition.start();</p><p>try {
// 等待 abort 或完成
await new Promise((_, reject) => {
controller.signal.addEventListener('abort', () => reject(controller.signal.reason), { once: true });
});
} finally {
recognition.stop();
}
}</p><p>// 使用流式消费
(async () => {
for await (const chunk of speechStream()) {
console.log('Stream chunk:', chunk.text);
if (chunk.isFinal) break; // 收到最终结果后退出
}
})();
注意事项与兼容性
Web Speech API 目前仅 Chromium 内核浏览器(Chrome、Edge)及 Safari(有限支持)提供,Firefox 暂不支持。生产环境务必做降级处理:
- 检测
window.SpeechRecognition是否存在,不存在则 fallback 到按钮点击录音+上传服务器识别 -
interimResults在部分设备上可能被忽略,不要依赖其稳定性 - 移动端需注意权限提示时机,首次调用
recognition.start()才触发授权弹窗 - 避免频繁创建/销毁
SpeechRecognition实例,可复用并重置状态(recognition.abort())
替代方案:基于 Web Audio + 第三方 SDK
若需更可靠、跨平台的流式识别,可绕过 Web Speech API,改用 MediaRecorder 录制音频流,通过 WebSocket 实时上传至支持流式 ASR 的后端(如 Vosk、Whisper.cpp 流式接口、Azure Speech SDK)。此时前端可封装成标准 ReadableStream 或自定义 EventEmitter,配合 AbortSignal 控制生命周期,灵活性更高,也规避了浏览器兼容问题。


















