• JavaScript进阶笔记(七):异步任务和事件循环


    JavaScript进阶笔记(七):异步任务和事件循环

    异步

    JS 是单线程的,对于耗时任务如果按照顺序执行,就会导致浏览器假死卡住。所以需要异步来处理耗时任务,当任务完成后才去处理。

    同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

    异步任务:不进入主线程,而进入任务队列中的任务,主线程完成一个事件循环空闲后,会从任务队列中读取新的任务进入主线程执行。

    事件循环(Event Loop):只有执行栈中的所有同步任务都执行完毕,系统才会读取任务队列,看看里面的异步任务哪些可以执行,然后那些对应的异步任务,结束等待状态,进入执行栈,开始执行。

    为什么JS要设计成单线程呢?

    异步的解决方案

    回调函数

    早期常用的异步操作方式,有个致命的缺点,极容易写出回调地狱。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ajax(url, ()=>{
    // xxx
    ajax(url,()=>{
    // xxx
    ajax(url, () => {
    // xxx
    })
    })
    })

    不利于代码阅读和维护,毕竟代码是用来读的顺便在机器上运行。不能使用 try-catch 不会异常。

    事件监听

    另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

    这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以”去耦合”(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

    发布订阅

    我们假定,存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”(publish)一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称”观察者模式”(observer pattern)。

    Promise

    ES6给我们提供了一个原生的构造函数Promise,用于异步操作可以将异步对象和回调函数脱离开来,通过 .then 方法在这个异步操作上绑定回调函数,Promise 可以让我们通过链式调用的方法去解决回调嵌套的问题,而且由于 promise.all 这样的方法存在,可以让同时执行多个操作变得简单。

    Promise 中存在三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

    关于Promise具体用法可以参考阮老师书中的《ES6入门-Promise》章。

    生成器Generator

    Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function *  hello () {
    yield 'hello'
    yield 'world'
    return 'ending'
    }
    const hl = hello()
    hl.next() // {value: "hello", done: false}
    h1.next() // {value: "world", done: false}
    h1.next() // {value: "ending", done: true}

    必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

    具体可以参考阮老师《ES6入门-Generator》章。

    async/await

    ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。

    Generator 使用太过复杂,通过 async/await 就比较简单了。

    async/await 对生成器进行改进,内置了执行器不需要在调用 next 方法。更好的语义,返回值是 Promise

    参考《ES6入门-async函数》

    参考

    事件循环

    Javascript 是单线程的,为了在处理异步任务的时候不会发生阻塞,提出了事件循环的解决方案。从宏观上来说,主线程在处理任务时,不会等待异步任务直到返回结果,而是将异步任务挂起,继续执行其他的任务。当异步任务返回结果不会立即处理而是加入到 事件队列 中。当主线程空闲时,读取事件队列中的任务,以此循环往复就形成事件循环。

    事件队列

    在事件循环中分为两种任务类型:宏任务(macro task) 和 微任务(micro task)。虽然都是异步任务但是两者的优先级不同,微任务属于人民币玩家拥有VIP特权。

    常见的宏任务:setIntervalsetTimeOut。微任务:Promise

    两种不同的任务对应着有两种不同的任务队列:宏任务队列 和 微任务队列。在事件循环中,异步任务的返回结果会根据不同的类型,放入不同的任务队列中。当主线程空闲时,会优先查看微任务队列,如果有任务依次执行任务直到微任务队列为空。然后去读取宏任务队列中的宏任务……依次循环,直到所有任务都完成。

    注意:由于微任务队列优先级高,所以同一事件循环中微任务优先执行。

    举个栗子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    console.log(1);
    setTimeout(function(){
    console.log(2);
    Promise.resolve(1).then(function(){
    console.log('promise')
    })
    })
    setTimeout(function(){
    console.log(3);
    })

    输出结果:

    1
    2
    3
    4
    1
    2
    promise
    3

    setTimeout 是宏任务,两个都被 Push 到宏任务队列中。而 Promise 是微任务,被 Push 到微任务队列中。当执行完第一个 setTimeout 会去读取微任务队列执行输出。然后在去执行下一个 setTimeout

    Node中事件循环不同于浏览器

  • 相关阅读:
    webkit webApp 开发技术要点总结
    EJB 教程推荐
    MySQL 教程分享
    php 教程列表
    html 学习资料列表
    JAVA 教程推荐
    php+mysql预查询prepare 与普通查询的性能对比
    Spring 5 新特性:函数式Web框架
    Java多线程之并发协作生产者消费者设计模式
    php使用file函数、fseek函数读取大文件效率分析
  • 原文地址:https://www.cnblogs.com/bamboopanders/p/13632531.html
Copyright © 2020-2023  润新知