Inspect the fixed example
The script clicks once, aborts the listener’s controller, then clicks again. The printed count records this fixed lifecycle, not cancellation of other work.
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<button id="action" type="button">Count local click</button><output id="result"></output>
<script>let count=0;const controller=new AbortController();const action=document.querySelector('#action');action.addEventListener('click',()=>count++,{signal:controller.signal});action.click();controller.abort();action.click();
const result=count;
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>1| Inspection | Supplied input or action | Result |
|---|---|---|
| First click | Live signal | count: 1 |
| abort() | signal.aborted | true |
| Second click | Listener removed | count: 1 |
Follow the native mechanism
The signal option associates this listener with the controller. Aborting removes it for subsequent events. An already-aborted signal does not register a new listener. Other listeners with another live signal can still run; the signal is not a global pause.
Compare the material alternative
Setting a separate disposed flag does nothing if the handler neither checks it nor gets removed. Both fixed clicks still increment.
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<button id="action" type="button">Count local click</button><output id="result"></output>
<script>let count=0,disposed=false;const action=document.querySelector('#action');action.addEventListener('click',()=>count++);action.click();disposed=true;action.click();
const result={disposed,count};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>{"disposed":true,"count":2}Work the boundary
Will a listener registered with an already-aborted signal run? What about a separate live controller?
Show the worked answer
The old signal’s listener never runs. The separate live listener runs once, then its abort prevents the next supplied click.
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<button id="action" type="button">Count local click</button><output id="result"></output>
<script>let oldCount=0,liveCount=0;const old=new AbortController();old.abort();const live=new AbortController();const action=document.querySelector('#action');action.addEventListener('click',()=>oldCount++,{signal:old.signal});action.addEventListener('click',()=>liveCount++,{signal:live.signal});action.click();live.abort();action.click();
const result={oldCount,liveCount,oldAborted:old.signal.aborted,liveAborted:live.signal.aborted};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>{"oldCount":0,"liveCount":1,"oldAborted":true,"liveAborted":true}