如何判断setInterval 是否正在执行?

如何判断setInterval 是否正在执行?

阅读 15.5k
4 个回答
var foo = window.setInterval(function(){
    ......
});

if (foo) {
    window.clearInterval(foo);
}

@manji setIntervalsetTimeout 返回的都是 Number,都是相应的 ID。

不过楼主需要的是判断 setInterval 是否在执行……

其实这个比较好的办法还是在执行函数里加一些处理 ,比如加一个计数器之类的,通过这个变量的变化了能了解它是否还在执行。比如

(function(ifun) {
    ifun.exeCount = 0
    setInterval(function() {
        ifun()
        ifun.exeCount++        
    }, 1000)
})(intervalFunction)

补充:2022-10-2

既然 @OceanZH 提到“优雅”,那就来实现一个优雅一点的。setInterval() 没有提供管理的机制,所以要从 setInterval() 返回的 id 来判断是否运行就比较困难。不过,如果对原生 interval 进行一层封装,把这个过程管理起来,就容易了。

下面的代码写了一个 ManagedInterval 类来管理,并使用 startInterval() 来开启(当然也可以实现一个 stopInterval() 来结束,只是这里没写)

class ManagedInterval {
    #fn;
    #interval;
    #timer;

    constructor(fn, interval) {
        this.#fn = fn;
        this.#interval = interval;
    }

    get active() { return !!this.#timer; }

    start() {
        if (this.#timer) { return; }
        this.#timer = setInterval(this.#fn, this.#interval);
    }

    stop() {
        clearInterval(this.#timer);
        this.#timer = undefined;
    }
}

function startInterval(fn, interval) {
    var mi = new ManagedInterval(fn, interval);
    mi.start();
    return mi;
}

// 试验:每 500 毫秒输出当时时间
var timer = startInterval(
    () => console.log(new Date()),
    500,
);

// 1200 毫秒后检查 interval 的活动状态 (true)
setTimeout(
    () => console.log("is active: ", timer.active),
    1200
);

// 2400 毫秒后停止 interval
setTimeout(
    () => timer.stop(),
    2400,
);

// 3000 毫秒后再次检查 interval 的活动状态 (false)
setTimeout(
    () => console.log("is active (stopped): ", timer.active),
    3000,
);

@边城
我重写了个管理类。注意stop的返回值

// interval 管理类
class ManagedInterval {
  //维护一个intervalId和对应活动状态的字典
  static dir = {}
  static active(id) {
    if (id in this.dir) {
      return this.dir[id]
    }
  }
  static start(fn, interval) {
    timer = setInterval(fn, interval)
    this.dir[timer] = true
    return timer
  }
  //返回值表示当前状态是否已经被打断
  static stop(id, flag = false) {
    let active = this.active(id)
    if (active) {
      clearInterval(id)
      this.dir[id] = false
    }
    if (flag) {
      this.remove(id)
    }
    return active
  }
  //在合适的时刻清理字典
  static remove(id) {
    if (id in this.dir) {
      delete this.dir[id]
    }
  }
}

根据题主的问题,以及我自己遇到的问题,遇到的需求可能是这样的:我们希望当前只有一个协程在执行,当协程结束后执行一个回调callback,比如维护一个timer表示当前唯一活动的协程的intervalIdtimer的状态有自然结束的,这时候应该执行callback,和被掐断的,即另一个协程开启,当前timer应该先掐断再执行另一个协程。
这时候就会有一个问题,需要通过timer是否活动来判断callback是否应该去执行(不活动就说明被其它协程掐断了)。使用我上面的管理类来简单演示一下。

//维护一个全局变量,表示当前唯一活动的协程
let timer
//逻辑定义在方法里
const fade = async (funcTODO, duraton, slerp) => {
  // 判空,确保只有一个协程存在
  ManagedInterval.stop(timer)
  timer = ManagedInterval.start(funcTODO, duraton / slerp)
  // 被打断时timer值会变化,故需保存当前的timer信息
  let tmpTimer = timer
  //休眠
  await sleep(duraton)
  // 分两种情况:
  // 1.正常结束,返回true,执行callback
  // 2.被打断,返回false,不执行callback
  res = ManagedInterval.stop(tmpTimer, true)
  if (res) {
    //callBack
  }
}

我试着实现了,现在它正在为我的另一个“玩具”做出贡献。@边城

class Eventable {
  static eventMap = {}
  // 务必确保没有重名的方法
  static on(type, handler) {
    type = type.toLowerCase()
    ;(this.eventMap[type] || (this.eventMap[type] = [])).push(handler)
  }
  static off(cfg) {
    if (cfg?.type && cfg?.handler) {
      let new_set = new Set(this.eventMap[cfg.type.toLowerCase()])
      if (new_set.has(cfg.handler)) {
        // console.log('方法删除成功')
        new_set.delete(cfg.handler)
        this.eventMap = [...new_set]
      }
    } else if (cfg?.type) {
      delete this.eventMap[cfg.type.toLowerCase()]
      //   console.log('任务删除成功')
    } else {
      this.eventMap = {}
      //   console.log('全部删除成功')
    }
  }
  static trigger(type) {
    this.eventMap[type.toLowerCase()]?.forEach(handler => {
      handler()
    })
  }
}

image.png
这里有一些manager和一个消息中心,我在消息中心把它们进行组合

它们很方便的在需要的地方调用(:

class Eventable {
  //不会ts,随便看看
  // {type->sting : handler->arr}
  static eventMap = {}
  // {type->string : handler->{any : args->arr}}
  static argsMap = {}
  // 务必确保没有重名的方法
  static on(type, handler, ...args) {
    type = type.toLowerCase()
    ;(this.eventMap[type] || (this.eventMap[type] = [])).push(handler)
    ;(this.argsMap[type] || (this.argsMap[type] = {}))[handler] = args
  }
  static off(cfg) {
    if (cfg?.type && cfg?.handler) {
      //event
      let new_set = new Set(this.eventMap[cfg.type.toLowerCase()])
      if (new_set.has(cfg.handler)) {
        new_set.delete(cfg.handler)
        this.eventMap = [...new_set]
      }
      //args
      delete this.argsMap[cfg.type.toLowerCase()][cfg.handler]
      // console.log('方法删除成功')
    } else if (cfg?.type) {
      //event
      delete this.eventMap[cfg.type.toLowerCase()]
      //args
      delete this.argsMap[cfg.type.toLowerCase()]
      //   console.log('任务删除成功')
    } else {
      //event
      this.eventMap = {}
      //args
      this.argsMap = {}
      //   console.log('全部删除成功')
    }
  }
  static trigger(type) {
    this.eventMap[type.toLowerCase()]?.forEach(handler => {
      handler(...this.argsMap[type.toLowerCase()][handler])
    })
  }
}

现在可以提供参数了!(:

经测试,var timer=window.setInterval(...),console.log(timer)是1,typeof timer的结果是number,我估计它是计时器的id,于是我做了个实验:

var i = 0;
var k = window.setInterval(function(){
i++;
console.log(i);
if(i>10){
    window.clearInterval(1);  // 注意这里,不是k是1
            // window.clearInterval(k);  // 跟上一句效果一样
}
},10);

上面的代码只输出到11,说明这真是个计时器的id,我试图在window对象里面找到那个能获取装这些id的容器的方法,但没找到。

推荐问题
宣传栏