前置常识

想要理解Async/Await关键字外部是怎么运行的?在什么机会运行?须要提前理解js异步编程,宏工作、微工作概念。

js异步编程

浏览器是多线程的,其蕴含如下多个线程:

  • js主引擎
  • ajax申请线程。
  • DOM解决线程。
  • 定时工作线程。
  • 其余线程。

咱们通常所说javascript是单线程的指的是js主引擎,其负责js代码的解释,预处理和执行。js主引擎保护一个执行栈,会依照程序顺次执行放入执行栈中的代码。如果某段代码执行工夫过长,那么残余代码会期待。

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主引擎会去查看音讯队列中是否有工作,如果有工作,就会取出最开始的工作将其放入执行栈中,执行栈会立刻执行该工作。

js代码执行过程就是执行栈顺次执行代码,执行实现后去音讯队列中取出工作放入执行栈继续执行,这个过程会一直的反复,也就形成Event Loop(事件循环)。

咱们通常应用回调函数来作为异步工作执行实现后执行的事件,然而当回调函数嵌套过深就会造成回调天堂,重大影响代码的可读性。

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)}

宏工作vs微工作

宏工作和微工作能够了解为异步工作的进一步细分,只不过执行机会不同,当某个宏工作执行结束之后,会查找微工作音讯队列中是否存在微工作,如果有,那么就执行所有微工作,而后再执行下一个宏工作。
上面是盗用的一张执行逻辑图:

常见的生成宏工作的有: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

下面讲过,Async/Await是为解决异步回调提出的计划,其本质是对Generator生成器进行包装的语法糖。

  • 如果async办法中没有await关键字,那么其就能够认为是一个一般的办法。
async function main() {    console.log('await')}console.log('next')main()// 输入// await// next
  • 如果await关键字前面是一个Promise对象,那么相当于在Promise对象中注入了then解决办法承受异步操作返回值,开启一个微工作,因而会先输入next,再输入await。
async function main() {    const v =await new Promise(resolve => {        resolve('await')    })    console.log(v)}main()console.log('next')// 输入// next// await
  • 如果await关键字前面的Promise对象中没有执行resolve办法,就会导致Promise始终处在pending状态,无奈执行then办法,因而await前面的代码不会执行,因而下例中的console.log(v)不会执行。
async function main() {    const v = await new Promise(resolve => { })    console.log(v)}main()console.log('next')// 输入// next
  • 如果await关键字前面是一个非Promise的一般数据,那么其相当于执行Promise.resolve()。
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)}