Js同步 异步 宏任务 微任务

420 阅读3分钟

之前遇到一个关于promise和async...await的执行顺序的题目,一直弄不懂,看了阮一峰老师的Promise 对象及winty的async/await 原理及执行顺序分析终于有一丢丢明白了。

  • 宏任务微任务
    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
    • micro-task(微任务):Promise,process.nextTick
    • 事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
  • Promise的要点
    • Promise新建后立即执行
    • then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行
    • 立即resolved的Promise是在本轮时间循环的末尾执行,总是脱于本轮循环的同步任务
  • async await要点
    • await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到async函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中。

题目一

 console.log(1)
setTimeout(() => {
  console.log(2)
  new Promise(resolve => {
    console.log(3)
    resolve(4)
  }).then(num => {
    console.log(num)
  })
  console.log(8)
}, 0);
new Promise(resolve => {
  console.log(5)
  resolve(6)
}).then(num => {
  console.log(num)
})
console.log(7)

setTimeout里面的函数肯定是最后执行的,Promise里面的then是在本轮循环的末尾执行,因此由上到下执行顺序为15762384

题目二

async function a1() {
    console.log('a1 start')
    await a2()
    console.log('a2 start')
  }
  async function a2() {
    console.log('a2')
  }
  console.log('script start')
  setTimeout(() => {
    console.log('setTimeout')
  }, 0);
  //此时产生第一个微任务
  Promise.resolve().then(() => {
    console.log('promise1')
  })
  a1()
  let promise2 = new Promise(resolve => {
    resolve('promise2.then')
    console.log('promise2')
  })
  promise2.then(res => {
    console.log(res)
    Promise.resolve().then(() => {
      console.log('promise3')
    })
  })
  console.log('script end')
  • 执行script宏任务,打印script start,遇到setTimeout宏任务,避开执行其他微任务,遇到Promise.then()在当前脚本所有同步任务执行完才会执行,执行a1(),打印a1 start-a2,再是打印promise2-script end,当前脚本所有同步任务执行完,将微任务按顺序执行promise1 - a2 start - promise2.then - promise3,此时当前宏任务执行完毕,执行setTimeout *运行结果:script start - a1 start - a2 - promise2 - script end - promise1 - a2 start - promise2.then - promise3 - setTimeout

题目三

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');

由于promise里没有传值,所以没有打印出'then',顺序为promise-console-setTimeout

题目四

process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)
  • 结果为 1-2-TIMEOUT FIRED
  • Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate
    • process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。
    • setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行。

题目五

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
  • process.nextTick和Promise.then()在当前"执行栈"的尾部
  • setTimeout:下一次Event Loop时执行
  • 答案1,7,6,8,2,4,3,5,9,11,10,12

题目六

console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)

new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})

console.log('script end')
  • 运行结果 script start-async2 end-Promise-script end-async1 end-promise1-promise2-setTimeout