我正在使用 MDN Web Speech API 读出一组单词,每个单词之间有一个短暂的延迟(对我儿子的拼写测试!)。我定义了一个异步函数来说一个词,并使用
setTimeout()
将后面的词延迟 5 秒。一切都按要求工作,除了在 5 秒超时解决之前,在 STOP 按钮之后立即按下 START 按钮。这导致整个单词数组重新开始,初始测试中的剩余单词穿插在两者之间。我试图通过取消setTimeout
方法并在超时激活时禁用开始按钮来解决这个问题,但没有成功。
// initiate the synth
const synth = window.speechSynthesis;
// grab the UI elements
const startButton = document.querySelector("#start");
let started = false;
const stopButton = document.querySelector("#stop");
stopButton.disabled = true;
// listen to the stop button
stopButton.addEventListener("click", () => {
startButton.disabled = false;
stopButton.disabled = true;
started = false;
synth.cancel();
});
// get the voices
const voices = synth.getVoices();
const GBvoice = voices.filter((voice) => {
return voice.lang == "en-GB";
});
// speak a single word
async function speakWord(word) {
const utterThis = new SpeechSynthesisUtterance(word);
utterThis.voice = GBvoice[1];
utterThis.pitch = 1;
utterThis.rate = 1;
synth.speak(utterThis);
}
// define delay function
const addDelay = (t) => {
return new Promise((resolve) => {
setTimeout(resolve.bind(null), t);
});
};
// define the spelling words
const words = ["column", "solemn", "autumn", "foreign", "crescent", "spaghetti", "reign", "fascinating", "whistle", "thistle"];
// problem - when start button is pressed during timeout, two lists of words are spoken
startButton.onclick = async function () {
startButton.disabled = true;
stopButton.disabled = false;
started = true;
for (let word of words) {
await speakWord(word).then(addDelay.bind(null, 5000));
if (!started) {
break;
}
}
};
<button id="stop">Stop</button>
<button id="start">Start</button>
AbortController
API 完成的取消路线。
为了演示的清晰(并且因为我的浏览器中没有可用的语音合成),所有与语音合成相关的部分都被删除并替换为裸露的
console.log
,但您应该能够轻松地将它们放回去。
let abc = null;
let jobCounter = 0;
const sleep = ms => new Promise(ok => setTimeout(ok, ms));
document.getElementById('btn.play').addEventListener('click', async ev => {
const job = ++jobCounter;
abc?.abort();
const myAbc = abc = new AbortController();
for (let i = 1; i <= 5; ++i) {
if (myAbc.signal.aborted)
break;
// await speakWord(...)
console.log(`job #${job} says: ${i}`);
await sleep(1000);
}
});
document.getElementById('btn.stop').addEventListener('click', ev => {
abc?.abort();
});
<button id="btn.play">PLAY</button>
<button id="btn.stop">STOP</button>
本质上:不是使用裸标志变量,而是每个“语音”作业创建一个新对象来管理自己的取消状态,然后将该对象放在一个全局变量中,任何一个按钮的处理程序都可以在其中找到它。单击任一按钮都会取消当前作业(如果有的话);然后播放按钮将开始一个新的工作。这样就避免了原来方法的ABA问题。
AbortController
替换为更简单的东西(例如具有 cancelled
属性的普通对象),但在一般情况下您需要调用其他 Web API(例如 fetch
),毕竟您可能需要准备好AbortController
。
检查一下
var unlock = {}
var test = new Promise((resolve)=>{unlock.resolve=resolve})
async function start() {
setTimeout(()=>{unlock.resolve()},5000)
}
async function stop() {
unlock.resolve()
}
async runFunction() {
await test
console.log('text')
}
或
async function start() {
return new Promise((resolve)=>{setTimeout(()=>{resolve()},5000)})
}
async function stop() {
start.resolve()
}
async runFunction() {
await start()
console.log('text')
}
--------编辑示例
var start = document.getElementById('start')
var stop = document.getElementById('stop')
var unlock = {}
start.addEventListener(
"click",
async () => {
var i=0;
while(i<=5) {
var test = new Promise((resolve)=>{unlock.resolve=resolve})
setTimeout(()=>{unlock.resolve('next')},1000)
var t = await test
if(t==='stop')
break
console.log('test', i)
i++
}
}
);
stop.addEventListener(
"click",
() => {
unlock.resolve('stop')
}
);
<button id='start'>start</button>
<button id='stop'>stop</button>