async/await
异步函数 async function
async关键字用于声明一个异步函数:
- async是asynchronous单词的缩写,异步、非同步;
- sync是synchronous单词的缩写,同步、同时;
async异步函数可以有很多中写法:
async function foo1() {}const foo2 = async () => {}class Foo {async bar() {}}
异步函数的执行流程
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
异步函数的返回值一定是一个 Promise,但是 return 后面的值会被包裹到 Promise.resolve 中
所以 return 不同的值会对返回的 Promise 请求成功与否造成影响
- 如果 return Promise 对象,返回的 Promise.resolve 的状态会由 return 后面的 Promise 决定;
- 如果 return 后面是一个对象并且实现了thenable,那么会由对象的 then 方法来决定; ```javascript async function foo1() { console.log(‘foo function start~’) console.log(‘foo function end~’) return 123 // return 普通值 }
async function foo2() { console.log(‘foo function start~’) console.log(‘foo function end~’) return new Promise(resolve => { // return promise resolve(‘promise success’) }) }
async function foo3() { console.log(‘foo function start~’) console.log(‘foo function end~’) return { // return thenable then(resolve, reject) { resolve(‘thenable success’) } } }
console.log(‘script start’)
foo1().then(res => { console.log(res) }) foo2().then(res => { console.log(res) }) foo3().then(res => { console.log(res) })
console.log(‘script end’)
// script start
// foo function start~
// foo function end~
// foo function start~
// foo function end~
// foo function start~
// foo function end~
// script end
// 123
// thenable success
// promise success
异步函数中的异常, 会被作为异步函数返回的 Promise 的 reject 值```javascriptasync function foo() {console.log("foo function start~")console.log("中间代码~")// 异步函数中的异常, 会被作为异步函数返回的Promise的reject值的throw new Error("error message")console.log("foo function end~")}// async 是个语法糖,会让异步函数的返回值一定是一个Promisefoo().catch(err => {console.log("coderwhy err:", err)})console.log("后续还有代码~~~~~")// foo function start~// 中间代码~// 后续还有代码~~~~~// coderwhy err: Error: error message
await 关键字
async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
await关键字有什么特点呢?
- 通常使用 await 是后面会跟上一个表达式,这个表达式会返回一个 Promise,或者表达式就是 Promise;
- 当 await 后面的 promise 状态为 fulfilled 时,await 会将 promise 的请求结果,也就是 resolve(res) 中的 res 返回。换句话说 await 是后面 promise 中 resolve(res) 的语法糖。
- 此时整个异步函数返回的 promise 没有什么意义,返回的promise.then 拿不到await 后面的 promise 的结果
- 并且 await 只有等到 Promise 的状态变成 fulfilled 状态,并获取请求成功的值之后,才继续执行下面的异步函数;
- await 下面的代码就像是定义在 then 函数中,因为要等到 await 后面表达式返回的 promise 得出正确结果才能执行。但是别忘记了 await 当前这一行和之前的代码都是同步执行的。 ```javascript // request.js function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { // 拿到请求的结果,这里 url 就是请求结果 resolve(url) }, 2000) }) }
async function getData() { const res1 = await requestData(‘hhh’) //效果和之前生成器函数中通过yield得到请求的结果一样 console.log(res1); //但是这里不用通过next().value.then()这样嵌套了,直接就像定义在了then中
const res2 = await requestData(res1 + ‘ tatakai’) console.log(res2); }
getData()
// Alan // Alan tatakai
如果 await 后面是一个普通的值,那么会直接返回这个值;<br />如果 await 后面是一个 thenable 的对象,那么会根据对象的 then 方法调用来决定后续的值;如果 await 后面的表达式,返回的 Promise 是 reject 的状态,那么会停止执行异步函数,并将这个 reject 结果直接**作为整个异步函数的 Promise 的 reject 值**;```javascript// await 跟普通值,直接返回async function foo1() {const res = await 123console.log(res)}foo1() // 123// 跟 thenable 对象async function foo2() {const res = await {then(resolve) {resolve('Alan')}}console.log(res)}foo2() // Alan// await 后面的 promise 请求失败async function foo3() {const result = await new Promise((resolve, reject) => {reject('failure') // await 后面的 promise 失败})console.log(result + '请求失败') // 不会执行,只有请求成功才会往下执行}foo3().catch(err => { // 整个异步函数的 promise.catch 捕捉到错误console.log(err)})// failure
事件循环
基础知识
进程和线程
操作系统 – 进程 – 线程
操作系统的工作方式
浏览器中的JavaScript线程
浏览器的事件循环
浏览器执行的过程
正常情况下,执行上下文进入执行上下文栈,同步代码一行一行往下执行,执行完毕就出栈。
如果同步代码往下执行,执行到 setTimeout 函数,setTimeout 也会被加入到执行栈中,并且执行完会出栈。但是 setTimeout 和普通函数不一样,它带有延迟的回调函数。在执行的时候,setTimeout 会将回调函数加入一个等待队列中,等全局同步代码执行完,回调函数就会被加入到执行栈中进行执行。
整个这个过程就构成了一轮循环:
同步代码开始执行 —> 进入执行上下文栈 —> 部分代码进入等待队列 —> 同步代码执行完 —> 等待队列中代码进入执行上下文栈,成为新的同步代码,开始下一轮执行。
js 同步执行线程 —— 执行栈 —— 等待队列 —— js 同步执行线程
宏任务和微任务
练习
setTimeout(function () {console.log("setTimeout1");new Promise(function (resolve) {resolve();}).then(function () {new Promise(function (resolve) {resolve();}).then(function () {console.log("then4");});console.log("then2");});});new Promise(function (resolve) {console.log("promise1");resolve();}).then(function () {console.log("then1");});setTimeout(function () {console.log("setTimeout2");});console.log(2);queueMicrotask(() => {console.log("queueMicrotask1")});new Promise(function (resolve) {resolve();}).then(function () {console.log("then3");});
setTimeout(function () { // 1. 加入宏任务队列,待执行console.log('setTimeout1') // 9. 进入同步队列立即执行new Promise(function (resolve) { // 10. 执行器立即执行resolve()}).then(function () { // 11. then 加入微任务队列,待执行new Promise(function (resolve) { // 12. 执行器立即执行resolve()}).then(function () { // 13. then4 添加到微任务,待执行console.log('then4') // 15. 清空微任务队列,执行 then4})console.log('then2') // 14. 立即执行,整个 then 执行完毕})})new Promise(function (resolve) {console.log('promise1') // 2. Promise 执行器回调函数立即执行,加入同步任务立即执行resolve() // 3. resolve 转到 then 函数}).then(function () { // 3.1 then 加入微任务队列,待执行console.log('then1')})setTimeout(function () { // 4. 加入宏任务队列,待执行console.log('setTimeout2') // 16. 清空宏任务,加入同步任务立即执行})console.log(2) // 5. 同步任务立即执行queueMicrotask(() => { // 6. 微任务函数,加入微任务队列,待执行console.log('queueMicrotask1')})new Promise(function (resolve) { // 7. 加入同步任务立即执行resolve()}).then(function () { // 8. then 加入微任务队列,待执行console.log('then3')})// 同步任务: promise1 2// 微任务:then1 queueMicrotask then3// 宏任务:setTime(1) setTime(2)// 同步任务执行完,清空微任务// 结果:promise1 2 then1 queueMicrotask1 then3// 清空宏任务// 同步任务:setTimeout1// 微任务:then// 宏任务:setTime(2)// setTime(1) 中添加了微任务,执行任何一个宏任务之前都得清空微任务// 所以 setTime(2) 暂时没执行,执行 then// 同步任务:then2// 微任务:then4// 宏任务:setTime(2)// 再次清空微任务队列,执行宏任务// 同步任务:then4 setTimeout2// 微任务:// 宏任务:// 结果:promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2
async function bar() {console.log('22222')return new Promise(resolve => {resolve()})}async function foo() {console.log('111111')await bar()console.log('33333')}foo()console.log('444444')async function async1() {console.log('async1 start')await async2()console.log('async1 end')}async function async2() {console.log('async2')}console.log('script start')setTimeout(function () {console.log('setTimeout')}, 0)async1()new Promise(function (resolve) {console.log('promise1')resolve()}).then(function () {console.log('promise2')})console.log('script end')
特别注意:异步函数返回 promise 对象的情况,这时 await 下面的‘then’,其实存在嵌套,需要加入下一轮的微任务队列。
async function bar() { // 1. 定义异步函数,待执行console.log('22222') // 6. 同步执行return new Promise(resolve => { // 7. 返回 promise 对象,这个要特别注意resolve() // 7.2 请求成功,将调用 then 方法})}async function foo() { // 2. 定义异步函数,待执行console.log('111111') // 4. 异步函数内部代码同步执行await bar() // 5. 同步执行 bar 异步函数 // 7.1 虽然 await 获取 promise 请求结果// 但是其实 await 不是直接获取的 7.2 中的resolve,中间有一个嵌套// bar函数的返回值准确应该是:Promise.resolve(new Promise())// 只是为了方便我们平时就直接看成 await 直接获取了 7.2 的 resolve 结果// 但是执行顺序上就不能省略这个嵌套直接看。8 这一行的 then 实际应该是 then(then())// 所以 33333 是放入了下一轮的微任务中console.log('33333') // 8. await 之下代码为请求成功后执行,相当于 then,// 8.1 then(then(33333))放入的微任务队列// 21. then(33333) 放入微任务队列}foo() // 3. 执行异步函数,加入同步队列console.log('444444') // 9. 同步执行async function async1() { // 10. 异步函数定义,待执行console.log('async1-start') // 15. 立即执行await async2() // 16. 同步执行 async2 异步函数 // 17.2 undefined 也就是请求成功但没有数据console.log('async1-end') // 18. 请求成功,‘then’ 放入微任务,待执行}async function async2() { // 11. 异步函数定义,待执行console.log('async2') // 17. 立即执行 // 17.1 没有return,相当于 return undefined}console.log('script-start') // 12. 同步代码立即执行setTimeout(function () { // 13. 定时器加入宏任务console.log('setTimeout') // 23. 立即执行}, 0)async1() // 14. 执行异步任务,加入同步队列new Promise(function (resolve) {console.log('promise1') // 19. 立即执行resolve()}).then(function () { // 20. 放入微任务console.log('promise2')})console.log('script-end') // 22. 立即执行// 同步任务:111111 22222 444444 script-start async1-start async2 promise1 script-end// 微任务:then(then(33333) async1-end promise2// 宏任务:setTimeout// 清空微任务,执行宏任务// 同步任务:async1-end promise2// 微任务:then(33333)// 宏任务:setTimeout// 清空微任务,执行宏任务// 同步任务:33333 setTimeout// 微任务:// 宏任务:// 结果:111111 22222 44444 script-start async1-start async2 promise1// script-end async1-end promise2 33333 setTimeout
Promise.resolve().then(() => {console.log(0);// 1.直接return一个值 相当于resolve(4)// return 4// 2.return thenable的值return {then: function(resolve) {// 大量的计算resolve(4)}}// 3.return Promise// 不是普通的值, 多加一次微任务// Promise.resolve(4), 多加一次微任务// 一共多加两次微任务return Promise.resolve(4)}).then((res) => {console.log(res)})Promise.resolve().then(() => {console.log(1);}).then(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}).then(() =>{console.log(6);})
Node 的事件循环
Node 架构
浏览器中的 EventLoop 是根据 HTML5 定义的规范来实现的,不同的浏览器可能会有不同的实现,而 Node 中是由 libuv 库实现的。
这里我们来给出一个Node的架构图:
- 我们会发现libuv中主要维护了一个EventLoop和worker threads(线程池);
- EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等
libuv 是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到 Luvit、Julia、pyuv 等其他地方;
application 的 js 代码给 V8 进行编译执行,其中一些需要依靠操作系统进行操作的地方,就会通过 bindings 传递给 libuv 来完成。
libuv 里面主要有事件队列和线程池,并且维护了他们之间的事件循环。那么这个循环是怎么来的?谁构成了循环?
阻塞式 IO 和 非阻塞式 IO

总结:js 调用操作系统进行 IO 操作,操作系统提供两种调用方式:阻塞式和非阻塞式,node 主要是基于非阻塞式。
非阻塞式 IO 的问题

总结:libuv 为了获取非阻塞式 IO 的结果,就会开启线程去轮询,并为此维护了一个线程池。一旦某个线程获取到 IO 结果,就会将 js 中读取结果的回调函数放入任务队列中等待执行。之后事件循环启动告知 js 主线程来执行任务队列中的函数。
阻塞和非阻塞,同步和异步的区别?
Node 事件循环的阶段

因为一个 tick 分为多个阶段,所以每个 tick 循环,node 都维护了多个任务队列。而浏览器只维护了两个队列,一个宏任务一个微任务。
Node 的宏任务和微任务
我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务:
- 宏任务(macrotask):setTimeout、setInterval、IO事件、setImmediate、close事件;
- 微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;
宏任务和微任务也是分布在队列中,所以也存在微任务队列和宏任务队列之分:
微任务队列:
- next tick queue:process.nextTick;
- other queue:Promise的then回调、queueMicrotask;
宏任务队列:
- timer queue:setTimeout、setInterval;
- poll queue:IO事件;
- check queue:setImmediate;
- close queue:close事件;
node 队列也有宏任务和微任务队列之分,那肯定队列之间也有执行顺序,并且微任务与微任务队列之间,宏任务与宏任务队列之间也有顺序:
- next tick microtask queue;
- other microtask queue;
- timer queue;
- poll queue;
- check queue;
- close queue;
总体上,微任务优先宏任务毋庸置疑,具体到函数,微任务中最高的是process.nextTick函数,然后是其他微任务函数,宏任务中最快的是 定时器函数,其次是 IO 函数,setImmediate函数,最后是 close 事件宏任务。
所以简单总结一下:**process.nextTick**> 普通微任务 > 定时器 > IO 函数 >**setImmediate** > close 事件
setTimeout(回调函数, 0)、setImmediate(回调函数)执行顺序分析
会出现两种情况:
- 情况一: setTimeout、setImmediate
- 情况二:setImmediate、setTimeout

练习
async function async1() {console.log('async1 start')await async2()console.log('async1 end')}async function async2() {console.log('async2')}console.log('script start')setTimeout(function () {console.log('setTimeout0')}, 0)setTimeout(function () {console.log('setTimeout2')}, 300)setImmediate(() => console.log('setImmediate'));process.nextTick(() => console.log('nextTick1'));async1();process.nextTick(() => console.log('nextTick2'));new Promise(function (resolve) {console.log('promise1')resolve();console.log('promise2')}).then(function () {console.log('promise3')})console.log('script end')
async function async1() {console.log('async1-start') // 7. 立即执行await async2() // 8. 立即执行 async2 函数console.log('async1-end') // 8.2 加入普通微任务}async function async2() {console.log('async2') // 8.1 立即执行}console.log('script-start') // 1. 同步立即执行setTimeout(function () { // 2. 定时器宏任务console.log('setTimeout0')}, 0)setTimeout(function () { // 3. 定时宏任务,但是会延迟 3 秒,故最后放入定时器宏任务console.log('setTimeout2')}, 3000)setImmediate(() => console.log('setImmediate')); // 4. check 宏任务队列process.nextTick(() => console.log('nextTick1')); // 5. 最快的微任务async1(); // 6. 执行异步函数process.nextTick(() => console.log('nextTick2')); // 9. 加入最快微任务队列new Promise(function (resolve) {console.log('promise1') // 10. 立即执行resolve(); // 11. 执行 thenconsole.log('promise2') // 12. 立即执行}).then(function () { // 11.1 then 放入普通微任务,待执行console.log('promise3')})console.log('script-end') // 13. 立即执行// 同步队列:script-start async1-start async2 promise1 promise2 script-end// 微任务:nextTick1 nextTick2// 普通微任务:async1-end promise3// 定时器宏任务:setTimeout0 setTimeout2// IO 宏任务:// check 宏任务:setImmediate// 3 秒后,定时器函数进入定时器宏任务队列// 同步队列:nextTick1 nextTick2 async1-end promise3 setTimeout0 setImmediate// 微任务:// 普通微任务:// 定时器宏任务:setTimeout2// IO 宏任务:// check 宏任务:// 结果:script-start async1-start async2 promise1 promise2 script-end// nextTick1 nextTick2 async1-end promise3 setTimeout0 setImmediate setTimeout2
