我们知道我们熟悉的javascript是一种单线程语言,一次只能完成一个任务,这种模式实现起来相对简单,但是这种模式有一个最大的弊端就是任务阻塞问题,当有一个任务非常耗时时,后面的任务必须排队执行,影响整个程序的执行。如:浏览器白屏,页面卡顿等。因此为了解决这一问题,js产生了异步执行模式。这种模式是不具有阻塞效应的,因此这种模式对于前端来说是必不可缺的。
1、js异步编程原理
我们知道js引擎负责解析和执行js代码,但它需要一个宿主环境来运行(浏览器或者node服务器,下文主要主要介绍浏览器,node环境参考node篇)。浏览器创建单一线程,通过事件循环(EventLoop),调用js引擎完成多个js代码块的调度。那么什么是事件循环呢,我们来看一下浏览器端的事件循环机制是什么样的。
1.1、同步任务与异步任务
同步任务:立即执行的任务为同步任务,同步任务都在主线程上执行,会形成一个执行栈(执行上下文 execution context stack)。
异步任务:异步任务包括宏任务与微任务,其中宏任务 :script(整体代码块)、setTimeout、setInterval、renderUI、I/O、setImmediate(node)。微任务:promise、Object.observe、MutationObserver、nextTick(node)、process(node)。一般异步任务放置在任务对列中等待读取。在一个事件循环中只有一个微任务队列,可有多个宏任务队列
1.2、事件循环eventLoop
事件循环:主线程不断的崇任务队列中读取执行事件的过程我们称为事件循环。时间循环的执行流程如下图:
(1)、执行栈选择当前要执行宏任务队列,选择其中最先执行的宏任务如果没有宏任务,执行后续的微任务
(2)、宏任务执行完成之后,将该宏任务重宏任务队列中移除,查找微任务队列执行,设置进入微任务对列标志,执行微任务队列中微任务,清空微任务队列,如果有失败的,则通知执行环境相应的失败情况,清理indexedDB事务。
(3)、更新渲染(浏览器自己选择是否更新视图)。
(4)、返回第一步
接下来看一段浏览器环境执行的代码:
上图我们可以根据浏览器的eventLoop来理解打印结果。
2、异步编程的实现方案
2.1、回调函数(callBack)
回调函数是异步模式最基本的实现方案,实现简单容易理解,是异步编程最原始的解决方案,但是由于它可读性和可维护性极差,并且各个部分之间高度耦合,程序结构混乱因此这种方案及容易导致回调地域等问题,跟开发者带来极其不好的体验。此外,在此方案中链路难以追踪,不能使用try catch捕获错误日志,也不能直接使用return ,导致在开发应用中出现错误难以定位,也可能导致很多闭环问题,所以大家在异步编程时,尽量选取更优的解决方案。
2.2、事件监听
事件监听的实现使异步任务的执行不取决于代码书写的顺序,而取决于事件发生与否。我们通常使用的一些DOM操作便是事件监听的实现。这种方法的优点比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化及功能拆分。但是它会使整个程序都要变成事件驱动型,运行流程会变得模糊不清。
2.3、发布/订阅模式或观察者模式
该模式定义了对象间一对多的关系,能够让观察者同时监听某一个主题的对象,当一个对象发生改变时,他所依赖的对象,都将获取到变更通知。该模式拥有一个信息中心的概念,当某一个任务完成时,便会通知信息中心,其他任务可以在这个信息中心订阅这个信息。发布/订阅模式是简单的广播通信的实现,任务之间的耦合性较低,但是也存在一定的内存、时间消耗。也不建议过度使用。下面我们简单用js 实现该模式:
2.4、promise
2.5、函数生成器(Generators)
2.6、async/await
2.7、响应式编程 rxjs
下一篇:前端异步编程(二篇)