最近在负责百度TDA平台的aiCalling二期,在给AI智能助手添加澄清功能的时候出现了一个需求:需要等待用户在输入框中输入内容并确认后,才能继续执行后续代码。简单来说,就是需要在同步代码流程中实现"阻塞等待"的效果。
澄清功能图如下:
需求伪代码如下:
javascript
while(条件1) {
while(条件2) {
// 一些代码
等待用户输入 // 需要在这里阻塞,直到用户输入后才继续
// 后续代码
}
}
当想到阻塞同步代码这个词条的时候我第一个想的就是async await
。
async
实际上就是Promsie
的语法糖,当 JavaScript
引擎遇到 async
函数时,会将其转换为一个返回 Promise
的函数。而在函数内部,每个 await
表达式都会暂停函数的执行,直到 Promise
解决,然后以 Promise
的解决值恢复执行(await
只暂停async
函数内部的执行,不会阻塞主线程,JavaScript
的事件循环会继续处理其他任务)。
我这里直接使用new Promise
通过await
来阻塞执行,等待resolve()
后结束阻塞:
javascript
async function() {
// 前置代码...
await new Promise(resolve => {
const checkTextEmpty = () => {
if (this.questionText !== '') { // 用户已输入并确认
// 一些逻辑代码...
resolve(); // 解除阻塞
} else {
setTimeout(checkTextEmpty, 200); // 200ms后再次检查
}
};
checkTextEmpty();
});
// 后续代码...
}
此时使用方案为:setTimeout
轮询+Promise
阻塞。
本文就此结束了吗?当然没有,肯定有同学会提出疑问:轮询一般是下下策,比如使用setTimeout
会来带性能损耗等诸多问题,有没有更好的解决方案?
有的兄弟有的。
在和mentor
进行code review
后,mentor
提出一个优化方案:通过现有事件总线的发布订阅来代替setTimeout
轮询。
于是优化版的代码如下:
javascript
// 处理用户输入的函数
setQuestionText = value => {
this.questionText = value;
if (value !== '') {
window.globalEvent.emitEvent('questionTextChanged', value);
}
};
// 主逻辑
async function() {
// 前置代码...
await new Promise(resolve => {
const eventHandler = event => {
// 一些逻辑代码...
event.remove(); // 移除事件监听,避免内存泄漏
resolve(); // 解除阻塞
};
//订阅用户输入事件
window.globalEvent.listenEvent('questionTextChanged', eventHandler);
});
// 后续代码...
}
此时的方案为:发布订阅+Promise
阻塞,这个方案是目前最终解决方案,避免了轮询方案递归setTimeout
的复杂性和潜在内存泄漏风险。
本文重在方案分享与探讨,欢迎来分享更优或平替解决方案。