同步任务与异步任务
同步任务:在主线程上排队执行的任务,按序执行调用,比如渲染页面的元素
异步任务:不仅如此主线程,而是进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行,比如某一张图片的加载,某一段音乐的加载
异步的执行方式
JS在处理任务的时候,可以分为同步任务和异步任务。只有当同步任务都执行完毕之后,才会到任务队列里面执行异步任务。异步任务执行的顺序是按事件循环的结果,执行的方式是回调函数
回调函数
一个异步任务被添加到任务队列中,等到异步操作完成后,就会在任务队列中添加一个事件,表示异步任务完成准备,可以进入执行栈,等主线程有空时,就会读取任务队列,执行里面的任务,如果该任务指定了回调函数,那么主线程在处理该事件时,就会执行回调函数中的代码,即执行异步任务。
事件循环
在任务队列中,也分为宏任务和微任务分别在宏任务队列和微任务队列,只有微任务队列中的任务全部执行完毕后,才会执行宏任务队列里面的任务。
宏任务:当前调用栈中执行的代码成为宏任务。script setTimeout, setInterval, setImmediate, I/O, UI rendering
微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件。process.nextTick, Promises, Object.observe, MutationObserver
每一次事件循环触发时:
先执行同步代码,
遇到异步宏任务则将异步宏任务放入宏任务队列中,
遇到异步微任务则将异步微任务放入微任务队列中,
当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
一直循环直至所有任务执行完毕。
注意:当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务;
异步的其他解决方案
回调函数
在上文已经提到过,不多赘述。他的优点是简单、易读、易实现,缺点是增加了维护的成本,提高了各部分的耦合度,让结构变得混乱,流程难以追踪(特别是出现回调嵌套的情况)。并且每个任务只能指定一个回调函数,不能用trycatch捕获异常。
事件监听
异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
通用性的事件监听方法:
- 绑定HTML元素属性
- 绑定DOM对象属性:document.getElementById(“xxx”).οnclick=test;
在DOM2级之后,事件监听函数为addEventListener,有三个参数:
第一个参数:事件的类型(DOM0绑定事件是直接写onclick,DOM2绑定事件的时候,要省略on)
第二个参数:绑定的监听者(个人理解就是回调函数=>也就是点击时要执行的函数)
第三个参数:是一个boolean类型的值(true:表示方法在捕获阶段时执行;false:表示在冒泡传播阶段执行)
发布订阅
存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
Generators/yield
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,最大的特点就是可以控制函数的执行。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态,还是一个遍历器对象生成函数。
可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
Promise
Promise是承诺的意思,承诺它过一段时间会给你一个结果。
它是一种解决异步编程的方案。
从语法上讲,promise是一个对象,从它可以获取异步操作的消息;
promise有三种状态:
pending 初始状态也叫等待状态
fulfiled成功状态
rejected失败状态
状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
主要特点
- Promise对象的状态不受外界影响
- Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆。
缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
- 当处于pending(等待)状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
应用场景
- 回调嵌套
- 多并发请求,获取并发请求的数据
常用API
Promise.resolve() 默认产生一个成功的 promise。
Promise.reject() 默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果。
Promise.prototype.catch() 用来捕获 promise 的异常,就相当于一个没有成功的 then。
Promise.prototype.finally() 如果返回一个 promise ,会等待这个 promise 也执行完毕。如果返回的是成功的 promise,会采用上一次的结果;如果返回的是失败的 promise,会用这个失败的结果,传到 catch 中。
Promise.all() 用来解决并发问题,多个异步并发获取最终的结果(如果有一个失败则失败)。
Promise.race() 用来处理多个请求,采用最快的(谁先完成用谁的)。
async/await
用同步的方式,编写异步。
async/await是基于Promise和Generators实现的,它不能用于普通的回调函数。
async和await 就是 Generators和 Promise的组合语法糖,将 generator 的 * 换成async,将 yield 换成await。
用法细节
- 函数前必须加一个 async,异步操作方法前加一个 await 关键字
- await 只能在 async 函数中运行,否则会报错
- Promise 如果返回的是一个错误的结果,如果没有做异常处理,就会报错,所以用 try..catch 捕获一下异常就可以了。
- await后面是一个pormise或者async函数会造成异步函数停止执行并且等待 promise 的解决,
- await后面是 正常的表达式则立即执行。
应用场景
- 连续相互依赖的请求
- 并发执行请求
- 错误处理
- 超时处理
- 并发限制