如何通过外部命令停止执行异步处理

时间:2020-01-05 04:42:46

标签: javascript promise

我希望这段代码能说明一切
第一个按钮启动异步命令(在此示例中为setTimeout),第二个按钮允许在旅途中停止其执行。

除了我的代码中没有像我想象的那样,而且我承认,我仍然很难满足诺言...。

目前我不明白为什么setTimeout不起作用?

我(仍然)想念什么?

(function(messageBox)
  {
  const texBox = document.getElementById('message-box')
    ;
  messageBox.txt = txt => texBox.textContent = txt 
  messageBox.add = txt => texBox.textContent += '\n'+txt
  }
(window.messageBox=window.messageBox || {}));

function timer()
{
  let status = 'none'  // inUse 
    , obj    = document.createElement('b')
    , ref    = null
    , msTime = 0
    ;
  const sleep  =_=> new Promise(res => ref=setTimeout(res, ms))
    ,   cancel =_=> new Promise(res => {
          obj.onclick =_=> {
            if (status==='inUse') {
              clearTimeout(ref)
              res()
        } } })
  async function start( ms ) {
    if (status==='none') {
      msTime = ms 
      status = 'inUse'
      await Promise.race([sleep,cancel])
      status = 'none'
    }
  }        
  function stop() { if (status==='inUse') obj.click() }

  return( { start, stop} )
}

const btStart = document.getElementById('bt-start')
  ,   btAbort = document.getElementById('bt-abort')
  ,   OnDelay = timer()
  ;
btStart.onclick=_=>
  {
  messageBox.txt('Start waitting 5 seconds...')
  OnDelay.start(5000)
  messageBox.add('next things to do...')
  }
btAbort.onclick=_=>
  {
  OnDelay.stop()
  messageBox.add('...abort !')
  }
#message-box {
  margin: 1em;
  border: 1px solid lightskyblue;
  width: 20em;
  height: 10em;
}
<button id="bt-start">wait 5s</button>
<button id="bt-abort">abort</button>
<pre id="message-box">message...</pre>

想法是:2个选项
a)单击按钮wait 5s => messageBox将立即显示Start waiting 5 seconds...
并且 5秒后,该程序将添加finish wait:)
messageBox =

开始等待5秒钟...
接下来要做的事情...

(它不符合我的期望)

b)单击按钮wait 5s => messageBox = Start waiting 5 seconds...(与= a =相同)

然后在按钮abort的5秒结束前单击之前 => messageBox =

开始等待5秒钟...
...中止!
接下来要做的事情...

5秒钟之前 (它也不如我所愿)

3 个答案:

答案 0 :(得分:1)

使用异步功能:

(function(messageBox) {
    const texBox = document.getElementById('message-box');
    messageBox.txt = txt => texBox.textContent = txt
    messageBox.add = txt => texBox.textContent += '\n' + txt
  }
  (window.messageBox = window.messageBox || {}));

function timer() {
  let timerID, rejectPromise;

  function start(nMS) {
    return new Promise((resolve, reject) => {
      timerID = setTimeout(_ => {
        timerID = undefined;
        resolve();
      }, nMS);
      rejectPromise = reject;
    });
  }

  function stop() {
    timerID && clearTimeout(timerID);
    timerID = undefined;
    rejectPromise();
  }

  return ({
    start,
    stop
  })
}

const btStart = document.getElementById('bt-start'),
  btAbort = document.getElementById('bt-abort'),
  OnDelay = timer();
  
btStart.onclick = async _ => {
  messageBox.txt('Start waitting 5 seconds...')
  try {
    await OnDelay.start(5000);
  } catch (_) {}
  messageBox.add('finish waitting :)')
}

btAbort.onclick = _ => {
  OnDelay.stop()
  messageBox.add('...abort !')
}
#message-box {
  margin: 1em;
  border: 1px solid lightskyblue;
  width: 20em;
  height: 10em;
}
<button id="bt-start">wait 5s</button>
<button id="bt-abort">abort</button>
<pre id="message-box">message...</pre>

注意:要等待5秒,然后 然后 做某事,您需要

  1. 有一个承诺p,5秒钟后由setTimeout解决,另一行说p.then(function() { doSomething() })
  2. 或者使用异步功能,并使用await来实现上述(1)中的承诺。

由于即使希望被拒绝,您也希望执行相同的操作,因此我在try... catch上添加了await。否则将引发错误,并且await下面的代码将不会继续。

在您的原始代码中,您调用了OnDelay.start(5000),它将一直运行到await行,以等待该诺言得到解决,并且控件返回到{{1}的下一行},它将立即显示。它不像一个同步程序,您可以在其中“睡眠” 5秒钟。仅当您在异步函数中完成所有操作并且messageBox.add('finish waitting :)')在5秒钟内解决,然后执行某项操作,或者使用诺言然后在5秒钟内解决,并将其设置为{{1 }}。

您的原始问题:

如何通过外部命令停止执行异步处理

有一个诺言等待解决,有await promise可以在5秒后解决。因此,您可以清除计时器,并拒绝承诺。如果您不拒绝(或解决)它,诺言将永远存在。 promise.then(doSomething)不会继续,或者setTimeout之后的行也不会继续。如果您拒绝诺言,则由thenawaitthen提供的第二个处理程序来处理诺言。如果您使用异步功能,则该catch将引发错误,您可以使用finally来处理或忽略它。

使用承诺而不是异步功能:

由于即使希望被拒绝,您也希望执行相同的操作,因此请使用await而不是try-catch

finally
then
(function(messageBox) {
    const texBox = document.getElementById('message-box');
    messageBox.txt = txt => texBox.textContent = txt
    messageBox.add = txt => texBox.textContent += '\n' + txt
  }
  (window.messageBox = window.messageBox || {}));

function timer() {
  let timerID, rejectPromise;

  function start(nMS) {
    return new Promise((resolve, reject) => {
      timerID = setTimeout(_ => {
        timerID = undefined;
        resolve();
      }, nMS);
      rejectPromise = reject;
    });
  }
  
  function stop() {
    timerID && clearTimeout(timerID);
    timerID = undefined;
    rejectPromise();
  }

  return ({
    start,
    stop
  })
}

const btStart = document.getElementById('bt-start'),
  btAbort = document.getElementById('bt-abort'),
  OnDelay = timer();
  
btStart.onclick = _ => {
  messageBox.txt('Start waitting 5 seconds...')
  OnDelay.start(5000).catch(err => {}).finally(_ => messageBox.add('finish waitting :)'));
}

btAbort.onclick = _ => {
  OnDelay.stop()
  messageBox.add('...abort !')
}

答案 1 :(得分:1)

我在下面添加了一些简单的代码,它们可以模拟您所需的执行,并有助于解决您的查询。

(() => {
  const messageBox = document.getElementById('message-box');       
  const btStart = document.getElementById('bt-start');
  const btAbort = document.getElementById('bt-abort');
  const interval = 5000;
  
  let objTimeout = null;  
  btStart.onclick = () => {
    messageBox.textContent += `\n [-] start waiting ${interval} ms...`;
    objTimeout = setTimeout(()=>{
    	messageBox.textContent += `\n [-] message  after ${interval} ms`;
    },interval)
  }
  btAbort.onclick = () => {  
    if(objTimeout){
    	clearTimeout(objTimeout);
      messageBox.textContent += `\n [x] printing aborted`;
    	messageBox.textContent += `\n [x] finish waiting`;
    }
  }

})();
#message-box {
  margin: 1em;
  border: 1px solid lightskyblue;
  width: 30em;
  height: 10em;
}
<button id="bt-start">wait 5s</button>
<button id="bt-abort">abort</button>
<pre id="message-box">waiting for message...</pre>

================================================ =================== 我在下面的摘要中发布了承诺。 仅当您想同步代码或防止竞争条件,或者想逐行执行语句时,才需要执行相同的诺言。

如果我们不确定在这种情况下诺言是否会解决或拒绝,我们应该使用setTimeout并解决或拒绝诺言。

我希望它能使您对此问题有所了解并有助于解决。

(() => {
  const messageBox = document.getElementById('message-box');
  const btStart = document.getElementById('bt-start');
  const btAbort = document.getElementById('bt-abort');
  const interval = 5000;
  let objTimeout = null;
  let objPromise = null;

  const appendMessage = (message) => {
    messageBox.textContent += message;
  }
  
  const addMessage = (message) => {
    messageBox.textContent = message;
  }

  const fnPromiseResolve = () => {
    console.log("promise resolved");
  };

  const fnPromiseReject = () => {
    clearTimeout(objTimeout);
    appendMessage(`\n [x] printing aborted`);
    appendMessage(`\n [x] finish waiting`);
    objTimeout = null;
  };

  btStart.onclick = () => {
    objTimeout = setTimeout(() => {
      Promise.resolve().then(() => {
        addMessage(`\n [-] start waitting ${interval} ms`);
      }).then(() => {
        appendMessage(`\n [-] message  after ${interval} ms`);
      }, interval).then(() => {
        appendMessage(`\n [-] printing finished`);
        objTimeout = null;
      });
    }, interval);
  }

  btAbort.onclick = () => {
    if (objTimeout !== null) {
      Promise.reject(new Error('printing aborted'))
        .then(fnPromiseResolve, fnPromiseReject);
    }
  }

})();
#message-box {
  margin: 1em;
  border: 1px solid lightskyblue;
  width: 30em;
  height: 10em;
}
<button id="bt-start">wait 5s</button>
<button id="bt-abort">abort</button>
<pre id="message-box">waiting for message...</pre>

答案 2 :(得分:1)

我认为您对这个问题的想法过多。

您只需要一个 Promise及其通常的.then.reject.finally()。不需要.race

此外,您还可以将所有这些操作包装到一个类中,作为一种更干净,可重用的方法:

const btStart = document.getElementById('bt-start')
  ,   btAbort = document.getElementById('bt-abort')
  ;
  btAbort.disabled = true
  ;
const messageBox = document.getElementById('message-box')
  ;
  messageBox.txt = txt => messageBox.textContent = txt;
  messageBox.add = txt => messageBox.textContent += '\n' + txt;

class Countdown {
  constructor(onStart, onEnd, onAbort)
    {
    this.timeout       = null;
    this.handleCatch   = onAbort;
    this.handleFinally = onEnd;
    this.starter       = (res, rej) => {
                            onStart();
                            this.timeout = setTimeout(res, 5000);
                            // Pass the `reject` function to a property to be used later
                            this.aborter = rej;
                          };
    }
  async start()
    {
    try 
      {
      // Only one `Promise` is needed.
      this.main = await new Promise(this.starter);
      } 
    catch(e)  /* This will run on abort */
      {
      // Stop the timeout
      clearTimeout(this.timeout);
      // Reset the property to make sure `abort` can only be run once for each valid timeout.
      this.aborter = null;
      this.handleCatch();
      } 
    finally /* This will run after everything ends */
      {
      this.handleFinally();
      } 
    }
  abort()
    {
    if (typeof this.aborter === 'function')
      {
      // Use the `reject` that is passed to this property earlier.
      this.aborter();    
      }
    }
  }

const countdown = new Countdown(
      /* onStart  */ ()=> {
                          btAbort.disabled = false
                          messageBox.txt('Start waiting 5 seconds...')
                          },
      /* onEnd    */ ()=> {
                          btAbort.disabled = true
                          messageBox.add('next things to do...')
                          },
      /* onAbort  */ ()=> messageBox.add('...abort!')
  )

btStart.addEventListener('click', countdown.start.bind(countdown));
btAbort.addEventListener('click', countdown.abort.bind(countdown));
#message-box {
  margin : 1em;
  border : 1px solid lightskyblue;
  padding: .3em;
  width  : 22em;
  height : 5em;
}
<button id="bt-start">wait 5s</button>
<button id="bt-abort">abort</button>

<pre id="message-box">message...</pre>

这样,不仅您的代码是干净的,而且您可以在代码中的任何地方重用倒计时。

您需要做的就是告诉实例onStartonEndonAbort该做什么。

[edit(PO)]
添加了一些改进:
-禁用/启用中止按钮
-将消息从finish waiting :)更改为next things to do...,这样可以更清楚地了解这种确定性
+将样式更改为Whitesmiths样式,因为我发现可以更清晰地分析和理解代码。