美文网首页
Javascript 异步编程(三)定时器

Javascript 异步编程(三)定时器

作者: 夏末远歌 | 来源:发表于2020-04-05 02:45 被阅读0次

    Javascript 异步编程(三)

    并行?并发?异步?

    同步:synchronous: 指所有任务按出现的先后顺序依次执行 如果出现阻塞的任务,那么线程就会等待这个任务完成,接着执行下一个任务。

    异步:asynchronous:不保证所有任务按出现的顺序执行

    并发:concurrent:从宏观上,某个时间段里面多个程序都得到了运行,但不是说“同时运行”

    并行:parallel:在多核心下,因进程和线程独立运行,且多个线程之间共享数据,程序可以同时运行。

    定时器

    常用的回调函数有:

    • setTimeout
    • setInterval
    • setImmediate(Node.js)
    • requestAnimationFrame

    https://zhuanlan.zhihu.com/p/55129100

    setTimeout

    作用:延迟指定的时间来调用函数或计算表达式。

    语法:setTimeout(func /**函数,必选*/,code /**表达式,可选*/ ,milliseconds /**执行需等待的毫秒数,必选*/param1, param2, .../**传递给函数的参数,可选*/)

    具体用法参加《Javascript异步编程(一)》

    setInterval

    作用:按照指定的周期(以毫秒计)来调用函数或计算表达式。

    语法:setInterVal(func /**函数,必选*/,code /**表达式,可选*/ ,milliseconds /**每次执行将延迟的毫秒数,必选*/param1, param2, .../**传递给函数的参数,可选*/)

    原理

     console.log('sync...',1);
      setInterval(()=>{
        console.log('sync...',2)
      },2000);
      console.log('sync...',3)
    

    流程分析:

    1. 主程序调用栈
    2. 调用webAPI-setInterVal
    3. 定时器线程计数2s
    4. 每隔2s事件触发线程将回调放入任务队列
    5. 主线程通过Event Loop遍历任务队列执行回调 console.log('sync...',2)

    注意

    • IE9及以下版本不兼容(setTimeout&setInterval)传递额外参数,可使用polyfill
    • setInterval,delay最小为10ms。意味着无法设置为0秒间隔,但可以尝试使用postMessage实现
    • 使用clearInterval(timerId)关闭指定定时器
    • 确保回调函数执行时长小于delay时间
    • 同setTimeout一样,setInterval回调中的this永远指向global
    • 不推荐使用setInterval(code,delay),有安全风险

    为什么不建议使用setInterval

    如果任务实际耗时超过delay,会出现同一时间触发多个回调。

    有以下场景,每隔1s调用服务

    // 时间间隔大于delay
    let count = 5
    let intervalTimer = setInterval(function () {
      if (count <= 0) {
        clearInterval(intervalTimer)
        return
      }
      /*模拟延时任务*/
      let timeoutTimer = setTimeout(function (count) {
        console.log(`the ${count} is running`)
        clearTimeout(timeoutTimer)
      }, Math.floor(Math.random() * (10000) + 1000),count)
      count--
    }, 1000)
    
    image_9.png

    从上图可知,xhr响应无法按照顺序返回,这样就会导致无法正常处理结果

    折中方案

    function moreBetterInterval (count) {
      // 1s后调用
      setTimeout(function (countDown) {
        console.log(count +' is begin')
        let timeoutTimer = setTimeout(function (times) {
          console.log(`the ${times} is running`)
          clearTimeout(timeoutTimer)
          times--;
          if(times>0){
            moreBetterInterval(times)
          }
        }, Math.floor(Math.random() * (10000) + 1000),countDown)
      }, 1000,count)
    }
    moreBetterInterval(5)
    
    image_10.png

    可以保证递归之前已执行完回调,但无法保证按照一定的时间间隔。

    实际应用场景

    • 短信倒计时
    let countDown=60;
    let timer=setInterval(function () {
      countDown--;
      if(countDown<0){
        clearInterval(timer);
        countDown=60
      }
    },1000)
    
    • 显示当前时间
    setInterval(function () {
        let d = new Date()
        $('#clock')[0].innerHTML = d.toLocaleTimeString()
    }, 1000)
    

    setImmediate

    仅在Internet Explorer和Node.js下可用

    作用:在循环事件任务完成后马上运行指定代码

    语法:setImmediate(func /**函数,必选*/,code /**表达式,可选*/,[ param1,param2,...]/**传递给函数的参数,可选*/);

    setImmediate与setTimeout(func,0)?

    setTimeout(func,0)

    需要考虑

    • 是在哪个阶段注册的,如果在定时器回调或者I/O回调里面,setImmediate肯定先执行。如果在最外层或者setImmediate回调里面,哪个先执行取决于当时机器状况。
    • 当前的Event Loop的任务队列的情况,如果在队尾,那也要先执行前面的事件,这样也无法保证立即执行

    requestAnimationFrame

    作用:接受一个动画执行函数作为参数,这个函数的作用是仅执行一帧动画的渲染,并根据条件判断是否结束,如果动画没有结束,则继续调用requestAnimationFrame并将自身作为参数传入

    语法:requestAnimationFrame(func /**函数,必选*/)

    细节:以60FPS(每帧16.7ms)为目标,浏览器内部会选择渲染的最佳时机

    与setTimeout动画区别:

    • setTimeout(func,16.7):容易卡顿

    原因有两个:

    • 实际执行时间晚于设定的延迟时间,出现卡顿
    • 与浏览器刷新率有关,不同设备的屏幕刷新频率可能会不同,而setTimeout只能设定固定的时间间隔,无法保证与刷新率同步,容易丢帧
    • requestAnimationFrame能节省CPU开销,当元素隐藏或不可见时,会停止渲染。而setTimeout仍在后台执行。

    用途

    • 实现一帧的函数节流
    • 动画

    注意

    • 使用cancelAnimationFrame(id)关闭渲染动画
    • IE10以下不兼容,可使用setTimeout进行polyfill 从而模拟帧率尽量适配刷新率

    结尾

    通过以上定时器,最显著的共同点是:回调。

    初一看,回调没有问题呀,可以延迟计算。请想下以下情景:

    1. 嵌套回调
    let msg = document.getElementById('msg')
    $('#btn').click(function (evt) {
      msg.innerHTML += `${new Date()} processing btn click callback... <br>`
      setTimeout(function request () {
        msg.innerHTML += `${new Date()} processing setTimeout callback...<br>`
        $.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
          msg.innerHTML += `${new Date()} processing ajax callback...<br>`
        })
      }, 500)
    }) 
    

    这里我们用了三个函数嵌套,这种代码就被称为“回调地狱(callback hell)”,这样的代码难以编写,难以理解而且难以维护

    1. 控制反转
    $.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
      msg.innerHTML += `${new Date()} processing ajax callback...<br>`
    })
    

    即自己程序的一部分的执行控制权交由某个第三方。在你不确定这个回调能否按照预期执行时,发生意外时很难定位问题。

    为了优雅的的处理回调最大的问题:控制反转,有以下方式:

    • 回调分离 -->ES6 Promise
    • error-first风格-->Node.js中会将回调的第一个参数保留用作error
    const fs=require('fs')
    fs.readFile(__dirname,function(err,data) {
      if (err)  console.log(err)
      //...
    })
    

    但还是不优雅,并没有真正解决我们的控制反转问题,只是将我们之前担心的程序异常暴露了出来。

    可能现在你希望有API或其他语言机制来解决这些问题。所幸,ES6会给你带来些干货~

    Reference

    HTML Timers
    Timer resolution in browsers

    相关文章

      网友评论

          本文标题:Javascript 异步编程(三)定时器

          本文链接:https://www.haomeiwen.com/subject/xczephtx.html