JavaScript在浏览器里的执行流程跟在Node.js中同样,是基于事件循环的。javascript
事件循环:一个在JavaScript引擎等待任务、执行任务和休眠等待更多任务这几个状态之间的无穷无尽的循环。java
执行引擎通用的算法:算法
一个任务到来时引擎可能正处于运行状态,那么这个任务就被入队。多个任务组成了一个队列,命名为“宏任务队列”(v8 术语),引擎按照先进先出来处理它们,而后等待更多的任务(即休眠,几乎不消耗 CPU 资源)。promise
宏任务举例:浏览器
当引擎忙于执行一段 script 时,还可能有用户移动鼠标产生了 mousemove 事件,setTimeout 或许也恰好到期等这些事件,这些任务组成一个队列:markdown
当引擎处理任务时不会执行渲染,对于 DOM 的修改只有当任务执行完成才会被绘制。 若是一个任务执行时间过长,浏览器没法处理其余任务,在必定时间后就会在整个页面抛出一个如“页面未响应”的警示建议终止这个任务。这样的场景常常发生在不少复杂计算或者程序错误执行到死循环里。网络
使用 0 延时的 setTimeout(f)来计划一个新的宏任。 它被用来拆分一个计算耗费型任务为小片断,使浏览器能够对用户行为做出反馈和展现计算的进度。 也被用在事件处理函数中来定时执行一个行为,在当前事件被彻底处理(冒泡结束)以后。异步
微任务仅仅由咱们的代码产生。它们一般由 promises 生成:对于 .then/catch/finally 的处理函数变成了一个微任务。微任务一般"隐藏在" await 下,由于它也是另外一种处理 promise 的形式。函数
一个宏任务结束后,先执行全部微任务队列中的任务,而后再去执行其余宏任务或渲染。微任务优先级高保证了微任务中的程序运行环境基本一致(没有鼠标位置改变,没有新的网络返回数据,等等)。 有一个特殊的函数 queueMicrotask(func),能够将 func 加入到微任务队列来执行。若是咱们想要异步执行(在当前代码以后)一个函数,可是要在修改被渲染或者新的事件被处理以前,咱们能够用 queueMicrotask 来定时执行。spa
setTimeout(() => alert("timeout")); Promise.resolve() .then(() => alert("promise")); alert("code"); // code -> promise -> timeout 复制代码
Promise 的处理程序(handlers).then、.catch 和 .finally 都是异步的。 异步任务须要适当的管理。为此,JavaScript 标准规定了一个内部队列 PromiseJobs —— “微任务队列”(Microtasks queue)(v8 术语)。 这个队列先进先出,只有引擎中没有其余任务运行时才会启动任务队列的执行。 当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序就被放入队列中。等到当前代码执行完而且以前排好队的处理程序都完成时,JavaScript引擎会从队列中获取这些任务并执行。 即使一个 promise 当即被 resolve,.then、.catch 和 .finally 以后的代码也会先执行。 若是要确保一段代码在 .then/catch/finally 以后被执行,最好将它添加到 .then 的链式调用中。
let promise = Promise.resolve(); promise.then(() => alert("promise done")); alert("code finished"); // 该警告框会首先弹出 复制代码
指在 microtask 队列结束时未处理的 promise 错误。 microtask队列完成时,引擎会检查promise,若是其中任何一个出现rejected状态,就会触发unhandledrejection事件。 但若是在setTimeout里进行catch,unhandledrejection会先触发,而后catch才执行,因此catch没有发挥做用。
let promise = Promise.reject(new Error("Promise Failed!")); setTimeout(() => promise.catch(err => alert('caught'))); window.addEventListener('unhandledrejection', event => alert(event.reason)); // Promise Failed! -> caught 复制代码