共计 3836 个字符,预计需要花费 10 分钟才能阅读完成。
前置常识
想要理解 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)
}