JS异步开发

1,前言

  众所周知,JS语言是单线程的,在实际的开发过程中,JS的同步操作会阻塞页面乃至整个浏览器的运行,在用户看来就是页面卡住,只有同步操作进行完毕之后才会进行其他的处理,这种同步等待的用户体验极差,所以JS中引入了异步编程思想,主要就是不阻塞主线程的运行,用户直观的感受就是页面不会卡住.

2,概念

  2.1,浏览器的进程和线程

  首先可以确定一点是浏览器是多进程的,比如打开多个窗口可能就对应着多个进程,这样可以确保的是页面之间相互没有影响,一个页面卡死也并不会影响其他的页面。同样对于浏览器进程来说,是多线程的,比如我们前端开发人员最需要了解的浏览器内核也就是浏览器的渲染进程,主要负责页面渲染,脚本执行,事件处理等任务。为了更好的引入JS单线程的概念,我们将浏览器内核中常用的几个线程简单介绍一下

    (1):GUI渲染线程 负责渲染浏览器页面,解析html+css,构建DOM树,进行页面的布局和绘制操作,同事页面需要重绘或者印发回流时,都是该线程负责执行。

    (2):JS引擎线程 JS引擎,负责解析和运行JS脚本,一个页面中永远都只有一个JS线程来负责运行JS程序,这就是我们常说的JS单线程

        注意: JS引擎线程和GUI渲染线程永远都是互斥的,所以当我们的JS脚本运行时间过长时,或者有同步请求一直没返回时,页面的渲染操作就会阻塞,就是我们常说的卡死了

    (3):事件触发线程 接受浏览器里面的操作事件响应。如在监听到鼠标、键盘等事件的时候, 如果有事件句柄函数,就将对应的任务压入队列。

    (4):定时触发器线程 浏览器模型定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 它必须依赖外部来计时并触发定时。

    (5):异步http请求线程 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

  2.2,JS单线程

    因为只有JS引擎线程负责处理JS脚本程序,所以说JS是单线程的。可以理解的是js当初设计成单线程语言的原因是因为js需要操作dom,如果多线程执行的话会引入很多复杂的情况,比如一个线程删除dom,一个线程添加dom,浏览器就没法处理了。虽然现在js支持webworker多线线程了,但是新增的线程完全在主线程的控制下,为的是处理大量耗时计算用的,不能处理DOM,所以js本质上来说还是单线程的。

  2.3,同步异步

    同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

  2.4,任务队列

    任务队列就是用来存放一个个带执行的异步操作的队列,在ES6中又将任务队列分为宏观任务队列和微观任务队列。

    宏任务队列(macrotask queue)等同于我们常说的任务队列,macrotask是由宿主环境分发的异步任务,事件轮询的时候总是一个一个任务队列去查看执行的,"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。

    微任务队列(microtask queue)是由js引擎分发的任务,总是添加到当前任务队列末尾执行。另外在处理microtask期间,如果有新添加的microtasks,也会被添加到队列的末尾并执行

  2.5,事件循环机制

    异步时间添加到任务队列中后,JS引擎一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

    ES5的JS事件循环参考图:

  ES6的JS事件循环:

3,callback

   在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

因此callback 不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

4,promise

  ES6新增Promise对象的支持,Promise提供统一的接口来获取异步操作的状态信息,添加不能的处理方法。

  Promise对象只有三种状态

  1. pendding: 初始状态,既不是成功,也不是失败状态。
  2. fulfilled: 意味着操作成功完成。
  3. rejected: 意味着操作失败。

  Promise的状态只能由内部改变,并且只可以改变一次。

  用Promise来写异步可以避免回调地狱,也可以轻松的来实现callback需要引入控制代码才能实现的多个异步请求动作的需求

  当然Promise也有自己的缺点

  1. promise一旦新建,就会立即执行,无法取消
  2. 如果不设置回掉函数,promise内部抛出的错误就不会反应到外部
  3. 处于pending状态时,是不能知道目前进展到哪个阶段的 ( 刚开始?,即将结束?)  

5,async,await

  asycn/await方案可以说是目前解决JS异步编程的最终方案了,async/await是generator/co的语法糖,同时也需要结合Promise来使用。该方案的主要特点如下:

  • 普通函数,即所有的原子型异步接口都返回Promise,Promise对象中可以进行任意异步操作,必须要有resolve();
  • async函数,函数声明前必须要有async关键字,函数中执行定义的普通函数,并且每个执行前都加上await关键字,标识该操作需要等待结果。
  • 执行async函数。asynch函数的返回值是Promise对象,可以用Promise对象的then方法来指定下一步操作

摘自掘金:https://juejin.im/post/5cff4f3df265da1b86087dd7#heading-7

原文地址:https://www.cnblogs.com/sunshengwei/p/11010351.html