如何在 setTimeout 期间禁用按钮,或在单击按钮时取消 setTimeout

问题描述 投票:0回答:2

我正在使用 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>

javascript promise settimeout cancellation webspeech-api
2个回答
2
投票

这似乎有点过度设计,但这是使用

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


-2
投票

检查一下

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>

© www.soinside.com 2019 - 2024. All rights reserved.