首先 js 是单线程(当然 web worker 可以创建新的线程), js单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行(如下图)。
task queue(上图的任务队列) 分为:
- macro-task (task/宏任务)由js外部宿主提供的 如: 渲染事件(如解析 DOM、计算布局、绘制);用户交互事件(如鼠标点击、滚动页面、放大缩小等);JavaScript 脚本执行事件;网络请求完成、文件读写完成事件。
- micro-task (jobs/微任务)由js自己提供的,例如: process.nextTick, Promise, Object.observe(已废弃), MutationObserver( 核心就是采用了微任务机制,有效地权衡了实时性和执行效率的问题。 )
setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务
task queue的入队顺序如下面的代码
for (const macroTask of macroTaskQueue) { handleMacroTask(); for (const microTask of microTaskQueue) { handleMicroTask(microTask); } }
执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask
)队列是否为空,如果为空的话,就执行Task
(宏任务),否则就一次性执行完所有微任务。
每次单个宏任务执行完毕后,检查微任务(microTask
)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask
)后,设置微任务(microTask
)队列为null
,然后再执行宏任务,如此循环。
以下是案例:
setTimeout(function() { console.log('timeout1'); }) new Promise(function(resolve) { console.log('promise1'); for(var i = 0; i < 1000; i++) { i == 99 && resolve(); } console.log('promise2'); }).then(function() { console.log('then1'); }) console.log('global1');
以上代码根据规则 :
- timeout1首先进入宏任务队列,
- Promise实例第一个参数不会进入其他队列,在当前任务执行,then里面的函数进入微任务队列,
- 执行console.log(‘global1’);
- 这时候微任务队列里有 console.log(‘then1’); ,所以执行它,然后微任务队列为null
- 当前微任务队列是null,所以执行宏任务里的代码console.log(‘timeout1’);
所以最后的打印结果是:
- promise1
- promise2
- global1
- then1
- timeout1
有关async 和await
我们经常使用上面两个关键字来写同步代码,这个时候要了解的是他们做了什么
async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。
await 将等待 Promise 正常处理完成并返回其处理结果。 如果该值不是一个 Promise,await 会把该值转换为已正常处理的Promise,然后等待其处理结果。
示例:
async function async1() { console.log('async 1 start'); await async2(); console.log('async 1 end'); } async function async2() { console.log('async 2'); } async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); })
上面的代码可以理解为:
- async1里的 console.log(“async 1 start”)可视为promise里面的同步代码,在当前执行栈中直接执行
- 执行到await时,async2里的console.log(“async 2”)与上面逻辑一样,而console.log(‘async 1 end’);可视为promise().then()里执行,进入微任务队列
- 再执行console.log(“promise1”),console.log(“promise2”)进入微任务队列
- 此时微任务队列按照进入的先后顺序进入执行栈执行,依次打印async 1 end 和promise2
所以最后结果为:
- async 1 start
- async 2
- promise1
- async 1 end
- promise2
ps: promise里的then的实现根据规范可以当做微任务,也可以是宏任务,以上代码都是在chrome里执行的,各个浏览器可能实现不一样
参考: