async/await

异步函数 async function

async关键字用于声明一个异步函数:

  • async是asynchronous单词的缩写,异步、非同步;
  • sync是synchronous单词的缩写,同步、同时;

async异步函数可以有很多中写法:

  1. async function foo1() {
  2. }
  3. const foo2 = async () => {
  4. }
  5. class Foo {
  6. async bar() {
  7. }
  8. }

异步函数的执行流程

异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行

异步函数的返回值一定是一个 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

  1. 异步函数中的异常, 会被作为异步函数返回的 Promise reject
  2. ```javascript
  3. async function foo() {
  4. console.log("foo function start~")
  5. console.log("中间代码~")
  6. // 异步函数中的异常, 会被作为异步函数返回的Promise的reject值的
  7. throw new Error("error message")
  8. console.log("foo function end~")
  9. }
  10. // async 是个语法糖,会让异步函数的返回值一定是一个Promise
  11. foo().catch(err => {
  12. console.log("coderwhy err:", err)
  13. })
  14. console.log("后续还有代码~~~~~")
  15. // foo function start~
  16. // 中间代码~
  17. // 后续还有代码~~~~~
  18. // 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

  1. 如果 await 后面是一个普通的值,那么会直接返回这个值;<br />如果 await 后面是一个 thenable 的对象,那么会根据对象的 then 方法调用来决定后续的值;
  2. 如果 await 后面的表达式,返回的 Promise reject 的状态,那么会停止执行异步函数,并将这个 reject 结果直接**作为整个异步函数的 Promise reject 值**;
  3. ```javascript
  4. // await 跟普通值,直接返回
  5. async function foo1() {
  6. const res = await 123
  7. console.log(res)
  8. }
  9. foo1() // 123
  10. // 跟 thenable 对象
  11. async function foo2() {
  12. const res = await {
  13. then(resolve) {
  14. resolve('Alan')
  15. }
  16. }
  17. console.log(res)
  18. }
  19. foo2() // Alan
  20. // await 后面的 promise 请求失败
  21. async function foo3() {
  22. const result = await new Promise((resolve, reject) => {
  23. reject('failure') // await 后面的 promise 失败
  24. })
  25. console.log(result + '请求失败') // 不会执行,只有请求成功才会往下执行
  26. }
  27. foo3().catch(err => { // 整个异步函数的 promise.catch 捕捉到错误
  28. console.log(err)
  29. })
  30. // failure

事件循环

基础知识

进程和线程

image.png

操作系统 – 进程 – 线程

image.png

操作系统的工作方式

image.png

浏览器中的JavaScript线程

image.png

浏览器的事件循环

image.png

浏览器执行的过程

正常情况下,执行上下文进入执行上下文栈,同步代码一行一行往下执行,执行完毕就出栈。
如果同步代码往下执行,执行到 setTimeout 函数,setTimeout 也会被加入到执行栈中,并且执行完会出栈。但是 setTimeout 和普通函数不一样,它带有延迟的回调函数。在执行的时候,setTimeout 会将回调函数加入一个等待队列中,等全局同步代码执行完,回调函数就会被加入到执行栈中进行执行。

整个这个过程就构成了一轮循环:

同步代码开始执行 —> 进入执行上下文栈 —> 部分代码进入等待队列 —> 同步代码执行完 —> 等待队列中代码进入执行上下文栈,成为新的同步代码,开始下一轮执行。

js 同步执行线程 —— 执行栈 —— 等待队列 —— js 同步执行线程

宏任务和微任务

image.png

练习

  1. setTimeout(function () {
  2. console.log("setTimeout1");
  3. new Promise(function (resolve) {
  4. resolve();
  5. }).then(function () {
  6. new Promise(function (resolve) {
  7. resolve();
  8. }).then(function () {
  9. console.log("then4");
  10. });
  11. console.log("then2");
  12. });
  13. });
  14. new Promise(function (resolve) {
  15. console.log("promise1");
  16. resolve();
  17. }).then(function () {
  18. console.log("then1");
  19. });
  20. setTimeout(function () {
  21. console.log("setTimeout2");
  22. });
  23. console.log(2);
  24. queueMicrotask(() => {
  25. console.log("queueMicrotask1")
  26. });
  27. new Promise(function (resolve) {
  28. resolve();
  29. }).then(function () {
  30. console.log("then3");
  31. });
  1. setTimeout(function () { // 1. 加入宏任务队列,待执行
  2. console.log('setTimeout1') // 9. 进入同步队列立即执行
  3. new Promise(function (resolve) { // 10. 执行器立即执行
  4. resolve()
  5. }).then(function () { // 11. then 加入微任务队列,待执行
  6. new Promise(function (resolve) { // 12. 执行器立即执行
  7. resolve()
  8. }).then(function () { // 13. then4 添加到微任务,待执行
  9. console.log('then4') // 15. 清空微任务队列,执行 then4
  10. })
  11. console.log('then2') // 14. 立即执行,整个 then 执行完毕
  12. })
  13. })
  14. new Promise(function (resolve) {
  15. console.log('promise1') // 2. Promise 执行器回调函数立即执行,加入同步任务立即执行
  16. resolve() // 3. resolve 转到 then 函数
  17. }).then(function () { // 3.1 then 加入微任务队列,待执行
  18. console.log('then1')
  19. })
  20. setTimeout(function () { // 4. 加入宏任务队列,待执行
  21. console.log('setTimeout2') // 16. 清空宏任务,加入同步任务立即执行
  22. })
  23. console.log(2) // 5. 同步任务立即执行
  24. queueMicrotask(() => { // 6. 微任务函数,加入微任务队列,待执行
  25. console.log('queueMicrotask1')
  26. })
  27. new Promise(function (resolve) { // 7. 加入同步任务立即执行
  28. resolve()
  29. }).then(function () { // 8. then 加入微任务队列,待执行
  30. console.log('then3')
  31. })
  32. // 同步任务: promise1 2
  33. // 微任务:then1 queueMicrotask then3
  34. // 宏任务:setTime(1) setTime(2)
  35. // 同步任务执行完,清空微任务
  36. // 结果:promise1 2 then1 queueMicrotask1 then3
  37. // 清空宏任务
  38. // 同步任务:setTimeout1
  39. // 微任务:then
  40. // 宏任务:setTime(2)
  41. // setTime(1) 中添加了微任务,执行任何一个宏任务之前都得清空微任务
  42. // 所以 setTime(2) 暂时没执行,执行 then
  43. // 同步任务:then2
  44. // 微任务:then4
  45. // 宏任务:setTime(2)
  46. // 再次清空微任务队列,执行宏任务
  47. // 同步任务:then4 setTimeout2
  48. // 微任务:
  49. // 宏任务:
  50. // 结果:promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2
  1. async function bar() {
  2. console.log('22222')
  3. return new Promise(resolve => {
  4. resolve()
  5. })
  6. }
  7. async function foo() {
  8. console.log('111111')
  9. await bar()
  10. console.log('33333')
  11. }
  12. foo()
  13. console.log('444444')
  14. async function async1() {
  15. console.log('async1 start')
  16. await async2()
  17. console.log('async1 end')
  18. }
  19. async function async2() {
  20. console.log('async2')
  21. }
  22. console.log('script start')
  23. setTimeout(function () {
  24. console.log('setTimeout')
  25. }, 0)
  26. async1()
  27. new Promise(function (resolve) {
  28. console.log('promise1')
  29. resolve()
  30. }).then(function () {
  31. console.log('promise2')
  32. })
  33. console.log('script end')

特别注意:异步函数返回 promise 对象的情况,这时 await 下面的‘then’,其实存在嵌套,需要加入下一轮的微任务队列。

  1. async function bar() { // 1. 定义异步函数,待执行
  2. console.log('22222') // 6. 同步执行
  3. return new Promise(resolve => { // 7. 返回 promise 对象,这个要特别注意
  4. resolve() // 7.2 请求成功,将调用 then 方法
  5. })
  6. }
  7. async function foo() { // 2. 定义异步函数,待执行
  8. console.log('111111') // 4. 异步函数内部代码同步执行
  9. await bar() // 5. 同步执行 bar 异步函数 // 7.1 虽然 await 获取 promise 请求结果
  10. // 但是其实 await 不是直接获取的 7.2 中的resolve,中间有一个嵌套
  11. // bar函数的返回值准确应该是:Promise.resolve(new Promise())
  12. // 只是为了方便我们平时就直接看成 await 直接获取了 7.2 的 resolve 结果
  13. // 但是执行顺序上就不能省略这个嵌套直接看。8 这一行的 then 实际应该是 then(then())
  14. // 所以 33333 是放入了下一轮的微任务中
  15. console.log('33333') // 8. await 之下代码为请求成功后执行,相当于 then,
  16. // 8.1 then(then(33333))放入的微任务队列
  17. // 21. then(33333) 放入微任务队列
  18. }
  19. foo() // 3. 执行异步函数,加入同步队列
  20. console.log('444444') // 9. 同步执行
  21. async function async1() { // 10. 异步函数定义,待执行
  22. console.log('async1-start') // 15. 立即执行
  23. await async2() // 16. 同步执行 async2 异步函数 // 17.2 undefined 也就是请求成功但没有数据
  24. console.log('async1-end') // 18. 请求成功,‘then’ 放入微任务,待执行
  25. }
  26. async function async2() { // 11. 异步函数定义,待执行
  27. console.log('async2') // 17. 立即执行 // 17.1 没有return,相当于 return undefined
  28. }
  29. console.log('script-start') // 12. 同步代码立即执行
  30. setTimeout(function () { // 13. 定时器加入宏任务
  31. console.log('setTimeout') // 23. 立即执行
  32. }, 0)
  33. async1() // 14. 执行异步任务,加入同步队列
  34. new Promise(function (resolve) {
  35. console.log('promise1') // 19. 立即执行
  36. resolve()
  37. }).then(function () { // 20. 放入微任务
  38. console.log('promise2')
  39. })
  40. console.log('script-end') // 22. 立即执行
  41. // 同步任务:111111 22222 444444 script-start async1-start async2 promise1 script-end
  42. // 微任务:then(then(33333) async1-end promise2
  43. // 宏任务:setTimeout
  44. // 清空微任务,执行宏任务
  45. // 同步任务:async1-end promise2
  46. // 微任务:then(33333)
  47. // 宏任务:setTimeout
  48. // 清空微任务,执行宏任务
  49. // 同步任务:33333 setTimeout
  50. // 微任务:
  51. // 宏任务:
  52. // 结果:111111 22222 44444 script-start async1-start async2 promise1
  53. // script-end async1-end promise2 33333 setTimeout
  1. Promise.resolve().then(() => {
  2. console.log(0);
  3. // 1.直接return一个值 相当于resolve(4)
  4. // return 4
  5. // 2.return thenable的值
  6. return {
  7. then: function(resolve) {
  8. // 大量的计算
  9. resolve(4)
  10. }
  11. }
  12. // 3.return Promise
  13. // 不是普通的值, 多加一次微任务
  14. // Promise.resolve(4), 多加一次微任务
  15. // 一共多加两次微任务
  16. return Promise.resolve(4)
  17. }).then((res) => {
  18. console.log(res)
  19. })
  20. Promise.resolve().then(() => {
  21. console.log(1);
  22. }).then(() => {
  23. console.log(2);
  24. }).then(() => {
  25. console.log(3);
  26. }).then(() => {
  27. console.log(5);
  28. }).then(() =>{
  29. console.log(6);
  30. })

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 来完成。
image.png
libuv 里面主要有事件队列和线程池,并且维护了他们之间的事件循环。那么这个循环是怎么来的?谁构成了循环?

阻塞式 IO 和 非阻塞式 IO

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

非阻塞式 IO 的问题

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

阻塞和非阻塞,同步和异步的区别?

image.png

Node 事件循环的阶段

image.png
因为一个 tick 分为多个阶段,所以每个 tick 循环,node 都维护了多个任务队列。而浏览器只维护了两个队列,一个宏任务一个微任务。
image.png

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 队列也有宏任务和微任务队列之分,那肯定队列之间也有执行顺序,并且微任务与微任务队列之间,宏任务与宏任务队列之间也有顺序:

  1. next tick microtask queue;
  2. other microtask queue;
  3. timer queue;
  4. poll queue;
  5. check queue;
  6. close queue;

总体上,微任务优先宏任务毋庸置疑,具体到函数,微任务中最高的是process.nextTick函数,然后是其他微任务函数,宏任务中最快的是 定时器函数,其次是 IO 函数,setImmediate函数,最后是 close 事件宏任务。

所以简单总结一下:**process.nextTick**> 普通微任务 > 定时器 > IO 函数 >**setImmediate** > close 事件

setTimeout(回调函数, 0)、setImmediate(回调函数)执行顺序分析

会出现两种情况:

  • 情况一: setTimeout、setImmediate
  • 情况二:setImmediate、setTimeout

image.png

练习

  1. async function async1() {
  2. console.log('async1 start')
  3. await async2()
  4. console.log('async1 end')
  5. }
  6. async function async2() {
  7. console.log('async2')
  8. }
  9. console.log('script start')
  10. setTimeout(function () {
  11. console.log('setTimeout0')
  12. }, 0)
  13. setTimeout(function () {
  14. console.log('setTimeout2')
  15. }, 300)
  16. setImmediate(() => console.log('setImmediate'));
  17. process.nextTick(() => console.log('nextTick1'));
  18. async1();
  19. process.nextTick(() => console.log('nextTick2'));
  20. new Promise(function (resolve) {
  21. console.log('promise1')
  22. resolve();
  23. console.log('promise2')
  24. }).then(function () {
  25. console.log('promise3')
  26. })
  27. console.log('script end')
  1. async function async1() {
  2. console.log('async1-start') // 7. 立即执行
  3. await async2() // 8. 立即执行 async2 函数
  4. console.log('async1-end') // 8.2 加入普通微任务
  5. }
  6. async function async2() {
  7. console.log('async2') // 8.1 立即执行
  8. }
  9. console.log('script-start') // 1. 同步立即执行
  10. setTimeout(function () { // 2. 定时器宏任务
  11. console.log('setTimeout0')
  12. }, 0)
  13. setTimeout(function () { // 3. 定时宏任务,但是会延迟 3 秒,故最后放入定时器宏任务
  14. console.log('setTimeout2')
  15. }, 3000)
  16. setImmediate(() => console.log('setImmediate')); // 4. check 宏任务队列
  17. process.nextTick(() => console.log('nextTick1')); // 5. 最快的微任务
  18. async1(); // 6. 执行异步函数
  19. process.nextTick(() => console.log('nextTick2')); // 9. 加入最快微任务队列
  20. new Promise(function (resolve) {
  21. console.log('promise1') // 10. 立即执行
  22. resolve(); // 11. 执行 then
  23. console.log('promise2') // 12. 立即执行
  24. }).then(function () { // 11.1 then 放入普通微任务,待执行
  25. console.log('promise3')
  26. })
  27. console.log('script-end') // 13. 立即执行
  28. // 同步队列:script-start async1-start async2 promise1 promise2 script-end
  29. // 微任务:nextTick1 nextTick2
  30. // 普通微任务:async1-end promise3
  31. // 定时器宏任务:setTimeout0 setTimeout2
  32. // IO 宏任务:
  33. // check 宏任务:setImmediate
  34. // 3 秒后,定时器函数进入定时器宏任务队列
  35. // 同步队列:nextTick1 nextTick2 async1-end promise3 setTimeout0 setImmediate
  36. // 微任务:
  37. // 普通微任务:
  38. // 定时器宏任务:setTimeout2
  39. // IO 宏任务:
  40. // check 宏任务:
  41. // 结果:script-start async1-start async2 promise1 promise2 script-end
  42. // nextTick1 nextTick2 async1-end promise3 setTimeout0 setImmediate setTimeout2