事件循环和同步异步
某日,群里有人发了一张这样的图,问输出结果为什么和预期的不一样,有几个人在讨论,都说不出个为什么。这一看就是一道面试题,考察了js的同步异步和事件循环,当然工作中类似的场景也非常多,这道题对于理解同类的问题会有一定的帮助。这里就这道题,解析一下它是如何执行的。在继续看下去之前,可以把你的答案先记下来。
1 | async function async1() { |
async
要理解这道题,首先要理解es2017 async 语法。
- async 函数返回promise对象,后面可以直接接then。
- 语法规定,async 的 await 命令后面,可以接promise对象或者原始类型的值(但是会转成立即resolve的promise对象。Promise.resolve())。
async的基本用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
下面是一个例子。函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。
1 | async function getStockPriceByName(name) { |
举个栗子,async3() 返回了promise对象
1 | // 预期输出 |
event loops
event loops是什么
Javascript是单线程的语言,既然是单线程的,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。 而浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中,按照一定的顺序进行执行。
event loops的任务类型
- 宏任务
- setTimeout
- setInterval
- requestAnimationFrame
- 解析HTML
- 执行主线程JS代码
- 修改URL
- 页面加载
- 用户交互
- 。。。
- 微任务
- Promise
- MutationObserver
- process.nextTick (nodejs)
- queueMicrotask
#### event loops的执行顺序(浏览器)
- 第一步,检查macrotask队列,运行最前面的任务,如果队列为空,前往第二步。
- 第二步,检查microtask队列,一直运行该队列任务直到该队列为空。
- 第三步,执行渲染ui。渲染后回到第一步。
执行过程解析
理解基本async语法和event loops机制后,可以写出执行过程如下
- 定义了async函数 async1
- 定义了async函数 async2
- 输出 script start
- 定义了一个setTimeout,该异步操作属于宏任务,推入macrotask队列
- 执行到了async1,进入async1
- 输出 async1 start
- 执行到了await语句,先执行async2(),输出 async2, async2()返回了一个promise对象,这个promise被推入microtask队列,await会阻塞当前执行函数体后续的语句,等待这个异步promise完成。
- 代码跳出async1继续执行
- 进入new Promise同步执行部分,输出 promise1
- 执行resolve,异步回调推入推入microtask队列
- 输出 script end
- 从microtask队列取出第一个异步任务执行,这个promise对象的resolve返回值为 undefined
- 继续执行async1里await后面的语句,输出 async1 end
- 从microtask队列取出第二个异步任务执行,输出 promise2
- 当前macrotask执行完毕
- 取出下一个macrotask执行,输出 setTimeout
最终输出结果
- script start
- async1 start
- async2
- promise1
- script end
- async1 end
- promise2
- setTimeout