代码
这段代码每秒检测condition是否为真
function safeClearInterval(id) {
if (!id) {
return;
}
clearInterval(id);
}
function monitor(condition, interval=1000) {
let timerId = null;
timerId = setInterval(() => {
if (!condition()) {
safeClearInterval(timerId);
}
}, interval);
}
clearInterval是完全安全的
这段代码什么都不会做
clearInterval(null);
所以上面的safeClearInterval(id)函数可以移除
function monitor(condition, interval=1000) {
let timerId = null;
timerId = setInterval(() => {
if (!condition()) {
clearInterval(timerId);
}
}, interval);
}
整理
由于setInterval中的函数是异步执行,可以整理为
function monitor(condition, interval=1000) {
const timerId = setInterval(() => {
if (!condition()) {
clearInterval(timerId);
}
}, interval);
}
向外暴露timerId
function monitor(condition, interval=1000) {
const timerId = setInterval(() => {
if (!condition()) {
clearInterval(timerId);
}
}, interval);
return timerId;
}
支持condition是异步函数
如果要支持condition是异步,那最好使用setTimeout代替interval,防止前一个condition还没结束,下一个condition就开始了
setTimeout 同步版本
每次调用setTimeout需要更新timerId,而timerId在浏览器环境是number类型(NodeJS环境是Timeout对象),直接返回外部无法监听更新,下面是错误的写法:
function monitor(condition, interval=1000) {
let timerId = null;
timerId = setTimeout(() => {
if (condition()) {
timerId = monitor(condition, interval);
}
}, interval);
return timerId;
}
正确应该用对象包围,或返回id的getter
function monitor(condition, interval=1000, timer={ id: null }) {
timer.id = setTimeout(() => {
if (condition()) {
monitor(condition, interval, timer);
}
}, interval);
return timer;
}
function monitor(condition, interval=1000) {
let timerId = null;
timerId = setTimeout(() => {
if (condition()) {
monitor(condition, interval);
}
}, interval);
const getTimerId = () => timerId;
return getTimerId;
}
实践中不会对外暴露timerId,而是暴露stop函数,这样让模块内外都有更好的扩展性。
function monitor(condition, interval=1000, timer={ id: null }) {
timer.id = setTimeout(() => {
if (condition()) {
monitor(condition, interval);
}
}, interval);
function stop() {
clearTimeout(timer.id);
}
return stop;
}
注意到,可以更规范地对外返回start函数,让外部控制何时开始
function monitor(condition, interval=1000) {
let timerId = null;
function start() {
timerId = setTimeout(() => {
if (condition()) {
start();
}
}, interval);
}
function stop() {
clearTimeout(timerId);
}
return {start, stop};
}
更多地,start可以接受一个immediate参数
function start(immediate=false) {
if (immediate && !condition()) {
return;
}
timerId = setTimeout(() => {
if (condition()) {
start();
}
}, interval);
}
每次循环都判断immediate && !condition()的返回值是低效的,应该把循环的代码提取出来:
function monitor(condition, interval=1000) {
let timerId = null;
function _loop() {
timerId = setTimeout(() => {
if (condition()) {
_loop();
}
}, interval);
}
function start(immediate=false) {
if (immediate && !condition()){
return;
}
_loop();
}
function stop() {
clearTimeout(timerId);
}
return {start, stop};
}
setTimeout 异步版本
即使condition为同步函数,await condition()也会将其包装为一个Promise,不需要开发者处理边缘情况。
function monitor(condition, interval=1000) {
let timerId = null;
async function _loop() {
timerId = setTimeout(async () => {
if (await condition()) {
_loop();
}
}, interval);
}
async function start(immediate=false) {
if (immediate && !(await condition())){
return;
}
_loop();
}
function stop() {
clearTimeout(timerId);
}
return {start, stop};
}
竞态条件处理
在执行setTimout()的回调函数执行时,在if (await condition())等待condition返回时,如果外部调用stop(),condition函数返回后,依旧会调用_loop,这意味着,该情况下stop调用是无效的。可以这样修复:
function monitor(condition, interval=1000) {
let timerId = null;
let isStopped = true;
async function _loop() {
if (isStopped) {
return;
}
timerId = setTimeout(async () => {
if (await condition()) {
_loop();
}
}, interval);
}
async function start(immediate=false) {
if (!isStopped) return; // 正在运行则直接返回
isStopped = false;
if (immediate && !(await condition())){
isStopped = true;
return;
}
_loop();
}
function stop() {
isStopped = true;
clearTimeout(timerId);
}
return {start, stop};
}
补充
虽然前面的代码具有一定的稳定性,但是在等待if (immediate && !(await condition())返回结果时调用stop,依旧需要condition执行完毕,如果condition需要10秒,那就会多进行10秒无意义的运行。这个问题可以借助AbortController解决。