浏览器JavaScript中如何实现或模拟忙等风格的输入交互?
适配忙等输入逻辑的低改动技术方案
以下方案按业务代码改动量从小到大排序:
方案1:Web Worker + SharedArrayBuffer 实现原生同步阻塞
这是改动最小的方案,原有业务逻辑代码无需任何修改,仅需替换print、input的底层实现即可:
- 把原有的命令行业务逻辑完全放在Web Worker中运行,避免阻塞主线程的UI渲染
- 主线程负责xterm.js的交互渲染,用户输入完成后将内容写入SharedArrayBuffer,通过
Atomics.notify通知Worker - Worker内的
input函数调用Atomics.wait进入阻塞状态,直到收到主线程的通知后读取输入内容返回,完全对齐原同步input的行为
核心实现示例
Worker侧逻辑:
// 初始化共享内存,前4字节存状态位,后面存输入内容 const sab = new SharedArrayBuffer(1024); const stateView = new Int32Array(sab, 0, 1); const contentView = new Uint8Array(sab, 4); // 把共享内存同步给主线程 self.postMessage({ type: 'init_sab', sab }); function input() { // 阻塞等待主线程输入完成通知 Atomics.wait(stateView, 0, 0); // 读取输入内容 const inputStr = new TextDecoder().decode(contentView).trim(); // 重置状态位供下一次输入使用 Atomics.store(stateView, 0, 0); return inputStr; } // print直接发消息给主线程调用xterm输出 function print(...args) { self.postMessage({ type: 'print', content: args.join('') }); } // 原有业务逻辑完全不用改 print("what is your name") name = input() print("hello ", name) print("how old are you") age = input()
主线程xterm适配逻辑:
import { Terminal } from 'xterm'; const term = new Terminal(); term.open(document.getElementById('terminal')); const worker = new Worker('worker.js'); let inputCache = ''; let sab, stateView, contentView; worker.onmessage = (e) => { if (e.data.type === 'print') { term.write(e.data.content + '\r\n'); } else if (e.data.type === 'init_sab') { sab = e.data.sab; stateView = new Int32Array(sab, 0, 1); contentView = new Uint8Array(sab, 4); } } // 监听xterm用户输入 term.onData((data) => { // 回车提交输入 if (data === '\r') { term.write('\r\n'); // 把输入内容写入共享内存 new TextEncoder().encodeInto(inputCache, contentView); // 修改状态位通知Worker Atomics.store(stateView, 0, 1); Atomics.notify(stateView, 0); inputCache = ''; return; } // 处理退格 if (data.charCodeAt(0) === 127) { if (inputCache.length) { inputCache = inputCache.slice(0, -1); term.write('\b \b'); } return; } // 普通字符输入 inputCache += data; term.write(data); });
注意事项
该方案需要网站配置跨域隔离响应头才能使用SharedArrayBuffer:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
方案2:Generator 函数轻量适配
如果无法配置跨域隔离头,可以选择该方案,仅需给原有代码的input()调用前加yield关键字,业务逻辑顺序完全无需调整:
// 仅需把原有逻辑包为Generator函数,input前加yield function* businessLogic() { print("what is your name") name = yield input() print("hello ", name) print("how old are you") age = yield input() }
然后写一个20行以内的执行器,每次Generator返回输入Promise时,等待用户输入完成后调用next传入输入值,继续执行后续逻辑即可,整体改动量不超过10%。
方案3:自动语法转译零手动修改
如果原有代码量极大,手动加yield的工作量也难以接受,可以写一个简单的AST转译脚本,自动识别所有input()调用并替换为适配逻辑,直接生成可运行的浏览器端代码,完全不需要手动修改业务代码。
内容的提问来源于stack exchange,提问作者user1200296
相关产品推荐
相关产品推荐

