JS 同步与异步
同步
函数在返回的时候能够拿到执行结果
异步
函数返回的时候,暂时得不到结果,将来通过一定手段(例如回调函数)得到结果,例如 AJAX 操作,定时器,文件异步读取等
单线程
JavaScript 是一门语言,单线程还是多线程由运行环境决定;在 js 的运行环境浏览器或者 Node 中 js 的执行是单线程的
单线程是指 js 引擎中负责解析执行 js 代码的线程只有一个
一个浏览器通常由以下几个常驻的线程:
- 渲染引擎线程,负责页面的渲染
- js 引擎线程,负责 js 的解析和执行
- 定时触发器线程,处理 setInterval 和 setTimeout
- 事件触发线程,处理 DOM 事件
- 异步 http 请求线程,处理 http 请求
Event Loop
js 实现异步的核心就是事件循环( Event Loop )
chrome 中的 Event Loop
过程
同步任务直接在主线程上排队执行,异步任务被放到相应的任务队列中,任务下一步会被移到调用栈(call stack),然后主线程执行调用栈的任务。
两种异步任务
宏任务( macrotask ):一会执行,setTimeout , setInterval
微任务( microtask ):马上执行, promise.then(fn) ,注意当 promise 实例 resolve 的时候 fn 回调才会放到微任务中
- 微任务优先宏任务执行
- 宏任务是在队列中按顺序逐个执行,微任务是一次执行完一整个队列
- 创建 Promise 实例时传入的函数是同步任务;new Promise(fn)
实例
Promise.resolve().then(()=>{ |
面试例题
async function async1(){ |
解析:
await async2() |
JavaScript同步和异步
深入理解js事件循环机制(浏览器篇)
Node.js 中的 Event Loop
启动
当 Node.js 启动时,会做这几件事:
- 初始化 event loop
- 开始执行脚本,这些脚本有可能会调用一些异步 API 、设定计时器或者调用 process.nextTick()
- 开始处理 event loop
事件循环
重要阶段解析
timers : 执行 setTimeout() 、setInterval() 的回调
poll (轮循) : 获取新的 I/O 事件, Node 会去检查有无已过期的 timer,如果有则把它的回调压入 timers 的任务队列中等待执行
check: 执行 setImmediate() 的回调
process.nextTick() 并不是 event loop 的一部分,不管 event loop 当前处于哪个阶段,nextTick 队列都是在当前阶段后就被执行了。
setImmediate() vs setTimeout(fn,0)
setImmediate() 的作用是在当前 poll 阶段结束后调用一个函数。
setTimeout() 的作用是在一段时间后调用一个函数。 即使 delay 设置为 0,最小也默认为4
直接运行时无法确定两者的执行顺序,
- 如果 Node 启动时间比较长,进入事件循环时,setTimeout() 已经超时了,就会直接在 timers 调用
- 如果 Node 启动时间比较短,进入 timers ,然后 poll ,然后在 check 调用了 setImmediate()
setTimeout(() => { |
如果把上面代码放到 I/O 操作的回调里,setImmediate 的回调就总是优先于 setTimeout 的回调
因为在读取文件在 poll 阶段拿到回调,会先经过 check
const fs = require('fs'); |
实例
setTimeout(()=>{ |