想要了解Async/Await关键字内部是怎么运行的?在什么时机运行?须要提早了解js异步编程,宏任务、微任务概念。javascript
浏览器是多线程的,其包含以下多个线程:java
咱们一般所说javascript是单线程的
指的是js主引擎,其负责js代码的解释,预处理和执行。js主引擎维护一个执行栈,会按照顺序依次执行放入执行栈中的代码。若是某段代码执行时间过长,那么剩余代码会等待。ajax
console.log('excute start') const date = new Date() while (new Date() - date < 3000) { // 模拟阻塞执行栈3秒 console.log('waiting') } // 3秒后才会执行 console.log('excute end')
可是,当咱们在js代码中调用浏览器其余的api(如定时任务)时,js主引擎就会执行异步操做,执行栈中的其余任务会继续执行,不用等待异步操做完成。编程
console.log('excute start') setTimeout(() => { // 3秒后当即执行 console.log('async excute') }, 3000); // 当即执行,不用等待3秒 console.log('excute end')
那么js主引擎怎么知道异步操做执行完毕呢?除了执行栈,还存在一个消息队列。其余api执行完异步操做后,会将回调事件放入消息队列中。当执行栈中的任务所有执行完毕后,js主引擎会去查看消息队列中是否有任务,若是有任务,就会取出最开始的任务将其放入执行栈中,执行栈会当即执行该任务。segmentfault
js代码执行过程就是执行栈依次执行代码,执行完成后去消息队列中取出任务放入执行栈继续执行,这个过程会不断的重复,也就构成Event Loop(事件循环)。api
咱们一般使用回调函数来做为异步任务执行完成后执行的事件,可是当回调函数嵌套过深就会造成回调地狱,严重影响代码的可读性。promise
ajax('ajax1', function () { ajax('ajax2', function () { ajax('ajax3', function () { ajax('ajax4', function () { // .... }) }) }) })
为了解决回调函数嵌套致使的回调地狱问题,ES2015提出了Promise方案,Promise经过链式调用让回调函数扁平化,减小了回调嵌套。浏览器
new Promise(resolve => { ajax('ajax1', function (data) { resolve(data) }) }).then(value => { return new Promise(resolve => { ajax('ajax2', function (data) { resolve(data) }) }) }).then(value => { return new Promise(resolve => { ajax('ajax3', function (data) { resolve(data) }) }) })
同时,ES中还包含Generator生成器,其配合执行器co函数也能够解决回调函数嵌套问题。多线程
function* main() { const v1 = yield new Promise(resolve => { setTimeout(() => { resolve('promise 1') }, 1000); }) console.log(v1) const v2 = yield new Promise(resolve => { setTimeout(() => { resolve('promise 2') }, 1000); }) console.log(v2) } // 执行器 function co(gen) { let generator = gen() function handleResult(result) { if (result.done) return else { result.value.then((value) => { handleResult(generator.next(value)) }, err => { console.log(err) }) } } handleResult(generator.next()) } // 执行 co(main)
为了更进一步简化异步编程语法,ES2017提出Async/Await语法糖,其本质就是包装了Generator生成器。异步
async function main() { let v1 = await new Promise(resolve => { setTimeout(() => { resolve('async excute 1') }, 1000); }) console.log(v1) let v2 = await new Promise(resolve => { setTimeout(() => { resolve('async excute 2') }, 1000); }) console.log(v2) } // 执行 main()
若是揭开语法糖,其至关于
function* main() { let v1 = yield new Promise(resolve => { setTimeout(() => { resolve('async excute 1') }, 1000); }) console.log(v1) let v2 = yield new Promise(resolve => { setTimeout(() => { resolve('async excute 2') }, 1000); }) console.log(v2) }
宏任务和微任务能够理解为异步任务的进一步细分,只不过执行时机不一样,当某个宏任务执行完毕以后,会查找微任务消息队列中是否存在微任务,若是有,那么就执行全部微任务,而后再执行下一个宏任务。
下面是盗用的一张执行逻辑图:
常见的生成宏任务的有:setTimeout(同等延迟时间下,setTimeOut的优先级高于setImmediate),setInterval, setImmediate, I/O, UI rendering。
常见的生成微任务的有:Promise,queueMicrotask。
setTimeout(() => { console.log('timeout') }, 0) new Promise(resolve => { resolve('promise') }).then(v => { console.log(v) })
上述代码中,因为Promise生成的是微任务,全部其早于setTimeout生成的宏任务,所以先输出promise,再输出timeout。
宏任务和微任务的执行过程能够用下面的代码简要说明:
// 事件循环, 主线程 while (macroQueue.waitForMessage()) { // 1. 执行完调用栈上当前的宏任务(同步任务) // call stack // 2. 遍历微任务队列,把微任务队里上的全部任务都执行完毕(清空微任务队列) // 微任务又能够往微任务队列中添加微任务 for (let i = 0; i < microQueue.length; i++) { // 获取并执行下一个微任务(先进先出) microQueue[i].processNextMessage() } // 3. 渲染(渲染线程) // 4. 从宏任务队列中取 一个 任务,进入下一个消息循环 macroQueue.processNextMessage(); }
上面讲过,Async/Await是为解决异步回调提出的方案,其本质是对Generator生成器进行包装的语法糖。
async function main() { console.log('await') } console.log('next') main() // 输出 // await // next
async function main() { const v =await new Promise(resolve => { resolve('await') }) console.log(v) } main() console.log('next') // 输出 // next // await
async function main() { const v = await new Promise(resolve => { }) console.log(v) } main() console.log('next') // 输出 // next
async function main() { const v = await 'await' console.log(v) } main() console.log('next') // 输出 // next // await // 至关于 async function main() { const v = await Promise.resolve('await') console.log(v) }
async function fn() { return 'foo' // 若是没有return,至关于return undefined } fn().then(data => { console.log(data) }) // 至关于 function fn1() { return new Promise(resolve => { resolve('foo') }) }