-
事件循环:同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
setTimeout(_ => console.log(4)) new Promise(resolve => { resolve() console.log(1) }).then(_ => { console.log(3) }) console.log(2) 解析: 整体script作为第一个宏任务开始执行: 遇见第一个setTimeout 将其放入第二个宏任务队列 执行new Peromise() resolve().then将其放入 第一个微任务队列 继续执行 打印 1 打印 2 第一个宏任务执行完毕 开始执行第一个宏任务队列里面的第一个微任务 打印3 第一个宏任务队列里面的微任务执行完毕 开始执行第二个宏任务 打印4
new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。同一次事件循环中,微任务永远在宏任务之前执行。
-
宏任务:(浏览器定义的)
-
宏任务中可以创建微任务,但是在宏任务中创建的微任务不会影响当前宏任务的执行。(将微任务 放置到当前宏任务队列的 微任务队列里面)
-
当一个宏任务队列中的任务全部执行完后,会查看是否有微任务队列,如果有就会优先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列
# 浏览器 NODE /O true fasle setTimeout true true setInterval true true requestAnimationFrame(这个是用于数据渲染) true true setImmediate false true -
宏任务中可以创建微任务,但是在宏任务中创建的微任务不会影响当前宏任务的执行。 -
可以有多个宏任务
-
-
微任务:(js引擎定义 因为并不是挂载到window上面的)
在上一个宏任务队列执行完毕后,如果有微任务队列就会执行微任务队列中的所有任务new promise((resolve)=>{ 这里的函数在当前队列直接执行 }).then( 这里的函数放在微任务队列中执行 )微任务队列上创建的微任务,仍会阻碍后方将要执行的宏任务队列(也就是会在当前微任务队列里 执行完毕)由微任务创建的宏任务,会被丢在异步宏任务队列中执行(会被放到 宏任务队列里面 也就是最后一个 如果当前宏任务是队列是1 那么被创建的宏任务 就是队列2 需要等到当前队列1的微任务执行完毕以后 才会执行 )
-
举例:
-
在微任务中创建微任务
setTimeout(_ => console.log(4)) new Promise(resolve => { resolve() console.log(1) }).then(_ => { console.log(3) Promise.resolve().then(_ => { console.log('before timeout') }).then(_ => { Promise.resolve().then(_ => { console.log('also before timeout') }) }) }) console.log(2); 解析: 第一次加载是第一个宏任务: 将setTimeout 放入第二个宏任务队列 继续执行 new Promise resolve() 将其then 放入第一个 宏任务队列里面的 第一个微任务队列 继续执行 打印1 继续打印 2 第一个宏任务队列执行完毕 开始执行第1个微任务 打印3 将第二个Promise.resolve().then放入 第一个宏队列的第2个微任务 继续执行第一个微任务的代码 以此类推 然后打印 before timeout also before timeout 现在第一个宏队列的微任务队列全部执行完毕 开始执行第二个宏队列 打印4 -
宏任务中创建微任务
// 宏任务队列 1 setTimeout(() => { // 宏任务队列 console.log('timer_1'); setTimeout(() => { // 宏任务队列 3 console.log('timer_3') }, 0) new Promise(resolve => { resolve() console.log('new promise') }).then(() => { // 微任务队列 1 console.log('promise then') }) }, 0) setTimeout(() => { // 宏任务队列 2 console.log('timer_2') }, 0) console.log('========== Sync queue ==========') 解析: 第一个宏任务是 整个scrpit 遇见第一个定时器 放入 第二个宏任务 继续执行 遇见第二个定时器 放入第三个宏任务 继续执行 输出========== Sync queue ========== 第一个宏任务执行完毕 又没有第一个宏任务的微任务 执行第二个宏任务 打印 timer_1 继续执行 将第三个定时器放入 第4个宏任务队列 继续执行new Promise 将then 放入第二个宏任务的第1个微任务 继续执行 打印console.log('new promise') 执行完毕 开始执行第二个宏任务的第一个微任务 console.log('promise then') 执行完毕 执行第三个宏任务 console.log('timer_2') 执行第4个 console.log('timer_3')
-
-
总结:
**微任务队列优先于宏任务队列执行,微任务队列上创建的宏任务会被后添加到当前宏任务队列的尾端,微任务队列中创建的微任务会被添加到微任务队列的尾端。只要微任务队列中还有任务,宏任务队列就只会等待微任务队列执行完毕后再执行。** -
对于包含async和await的异步执行
-
一旦遇到await 就立刻让出线程,阻塞后面的代码等候之后,对于await来说分两种情况
- 不是promise 对象:
- 如果await 后面直接跟的为一个变量,比如:
await 1;这种情况的话相当于直接把await后面的代码注册为一个微任务,可以简单理解为promise.then(await下面的代码)。然后跳出async1函数,执行其他代码,当遇到promise函数的时候,会注册promise.then()函数到微任务队列,注意此时微任务队列里面已经存在await后面的微任务。所以这种情况会先执行await后面的代码(async1 end),再执行async1函数后面注册的微任务代码(promise1,promise2)。 (举例demo1)
- 如果await 后面直接跟的为一个变量,比如:
- 是promise对象 (异步函数调用)
如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果 如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。demo1: async function async1() { console.log( 'async1 start' ) await async2() //相当于是柱塞后面的代码 放入当前宏任务队列里面的第一个微任务队列 console.log( 'async1 end' ) } async function async2() { console.log( 'async2' ) } async1() console.log( 'script start' ); 输出: async1 start -> async2 -> script start -> async1 end
async function async1() { console.log( 'async1 start' ) ---> 第一个输出 await async2() ---> 执行函数 函数执行完毕 返回到这里 然后退出 async1函数 console.log( 'async1 end' ) ---> 第5个输出 第一个宏任务的同步代码执行完毕 准备执行第一个宏任务的微任务 } async function async2() { console.log( 'async2' ) ---> 第二个输出 } console.log( 'script start' ) setTimeout( function () { console.log( 'setTimeout' ) ---> 第二个宏任务 }, 0 ) async1(); ----> 第一个执行 new Promise( function ( resolve ) { console.log( 'promise1' ) ---> 执行同步函数 第三个输出 执行完毕后 就返回到async1中继续执行 resolve(); } ).then( function () { console.log( 'promise2' ) ---> 第六个输出 } ) console.log( 'script end' ) ---> 第4个输出await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果
在执行 async 函数时,碰到了 await 的话,会立即执行紧跟 await 的语句,然后把后续代码推入微任务队列(以这种形式去理解,实际执行并非如此)。
const syn1 = () => console.log(2) const syn2 = () => new Promise((r, j)=>r()).then(()=>console.log(3)) async function asyncFunc () { console.log('start') await console.log(1); await syn1() await syn2() console.log('end') return 7 } setTimeout(() => console.log(5), 0) console.log(0) asyncFunc().then(v => console.log(v)) new Promise((r, j)=>r()).then(() => console.log(6)) console.log(4) 执行步骤: 第一步: 输入 0 执行第一个函数asyncFunc(); 第二步: asyncFunc() => 输入 start 等待执行完毕await console.log(1); 输入 1 然后跳出asyncFunc函数 就后面所以没有执行的内容全部推出优先级最高的微任务队列 第三步: 继续执行第一个宏任务 输入 4 第一个宏任务同步代码执行完毕 返回到asyncFunc()继续执行 第4步: 执行await syn1() 等待 输出2 然后 退出asyncFunc() 函数 再把后面没有执行的任务 推入微任务 第5步: 执行外面函数的微任务 输入 6 返回到asyncFunc()继续执行 第6步: 执行await syn2() 等待 输出3 然后 退出asyncFunc() 函数 再把后面没有执行的任务 推入微任务 第7步: 外部没有同步任务了 返回到asyncFunc()继续执行 第8步: 输出 end 并且返回7 第9步 : 执行第二个宏任务 输出5 - 不是promise 对象:
-
-
宏任务需要多次事件循环才能执行完,微任务是一次性执行完的; 一个宏任务完成以后 会把所有的微任务执行完毕
-
宏任务macrotask: (事件队列中的每一个事件都是一个macrotask)
优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval
比如:setImmediate指定的回调函数,总是排在setTimeout前面
- 微任务包括: 优先级:process.nextTick > Promise > MutationObserver