await、async、事件循环(宏任务、微任务队列执行顺序)

1 async、await

2 浏览器进程、线程

3 宏任务、微任务队列

4 Promise面试题解析

5 throw、try、catch、finally

异步函数-异步函数的写法

 // 普通函数
    // function foo() {}
    // const bar = function() {}
    // const baz = () => {}

    // 生成器函数
    // function* foo() {}

    // 异步函数
    async function foo() {
      console.log("foo function1")
      console.log("foo function2")
      console.log("foo function3")
    }
    foo()

    // const bar = async function() {}
    // const baz = async () => {}
    // class Person {
    //   async running() {}
    // }

异步函数-异步函数返回值

// 返回值的区别
    // 1.普通函数
    // function foo1() {
    //   return 123
    // }
    // foo1()

    // 2.异步函数
    async function foo2() {
      // 1.返回一个普通的值
      // -> Promise.resolve(321)
      return ["abc", "cba", "nba"]

      // 2.返回一个Promise
      // return new Promise((resolve, reject) => {
      //   setTimeout(() => {
      //     resolve("aaa")
      //   }, 3000)
      // })

      // 3.返回一个thenable对象
      // return {
      //   then: function(resolve, reject) {
      //     resolve("bbb")
      //   }
      // }
    }

    foo2().then(res => {
      console.log("res:", res)
    })

异步函数-异步函数的异常

在async里面的代码如果有报错和异常,那么不会影响后续代码,但是结果会放到promise的reject里面传递出来。

 // "abc".filter()

    // 什么情况下异步函数的结果是rejected

    // 如果异步函数中有抛出异常(产生了错误), 这个异常不会被立即浏览器处理
    // 进行如下处理: Promise.reject(error)
    async function foo() {
      console.log("---------1")
      console.log("---------2")
      // "abc".filter()
      throw new Error("coderwhy async function error")
      console.log("---------3")

      // return new Promise((resolve, reject) => {
      //   reject("err rejected")
      // })

      return 123
    }

    // promise -> pending -> fulfilled/rejected
    foo().then(res => {
      console.log("res:", res)
    }).catch(err => {
      console.log("coderwhy err:", err)
      console.log("继续执行其他的逻辑代码")
    })

异步函数-await关键字使用

await如果单纯用在普通变量和普通函数里面是没什么用的,它是用来配合promise来一起使用的,作用和yield差不多,就是等到promise有返回之后再接收结果然后再执行await下面几行的代码(就是会打断代码执行)。

// 1.普通函数
    // function foo1() {
    //   await 123
    // }
    // foo1()


    // 2.await关键字
    // await条件: 必须在异步函数中使用
    function bar() {
      console.log("bar function")
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(123)
        }, 100000)
      })
    }

    async function foo() {
      console.log("-------")
      // await后续返回一个Promise, 那么会等待Promise有结果之后, 才会继续执行后续的代码
      const res1 = await bar()
      console.log("await后面的代码:", res1)
      const res2 = await bar()
      console.log("await后面的代码:", res2)

      console.log("+++++++")
    }

    foo()

异步函数-await处理异步请求

在promise返回的是reject的时候,用catch可以捕获异常和错误。或者在getdata里面写try catch。

 function requestData(url) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(url)
          // reject("error message")
        }, 2000);
      })
    }

    async function getData() {
      const res1 = await requestData("why")
      console.log("res1:", res1)

      const res2 = await requestData(res1 + "kobe")
      console.log("res2:", res2)
    }

    getData().catch(err => {
      console.log("err:", err)
    })

异步函数-await和async结合

普通函数返回promise、但是异步async关键字创建的函数也一样可以在调用时候使用await。

  // 1.定义一些其他的异步函数
    function requestData(url) {
      console.log("request data")
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(url)
        }, 3000)
      })
    }

    async function test() {
      console.log("test function")
      return "test"
    }

    async function bar() {
      console.log("bar function")

      return new Promise((resolve) => {
        setTimeout(() => {
          resolve("bar")
        }, 2000);
      })
    }

    async function demo() {
      console.log("demo function")
      return {
        then: function(resolve) {
          resolve("demo")
        }
      }
    }


    // 2.调用的入口async函数
    async function foo() {
      console.log("foo function")

      const res1 = await requestData("why")
      console.log("res1:", res1)

      const res2 = await test()
      console.log("res2:", res2)

      const res3 = await bar()
      console.log("res3:", res3)

      const res4 = await demo()
      console.log("res4:", res4)
    }

    foo()

单线程-代码顺序如何执行

每个浏览器打开的页面都会成为一个进程,一个进程里面会有很多线程,但是执行JavaScript的线程只有一个。

 let name = "why"
    name = "kobe"

    function bar() {
      console.log("bar function")
    }

    function foo() {
      console.log("foo function")
      // 1.在JavaScript内部执行
      // let total = 0
      // for (let i = 0; i < 1000000; i++) {
      //   total += i
      // }

      // 2.创建一个定时器
      setTimeout(() => {
        console.log("setTimeout")
      }, 10000);

      bar()
    }

    foo()

单线程-异步代码如何执行

不管定时器定的时间是多少,都会先执行定时器后面的代码。

 事件队列,事件循环。

定时器、dom监听、网络请求都会被加到事件队列里面,然后事件队列按照先进先出的顺序把事件输入到执行上下文中。

<body>

  <button>按钮</button>
  
  <script>

    const btn = document.querySelector("button")
    btn.onclick = function() {
      console.log("btn click event")
    }

    console.log("Hello World")
    let message = "aaaa"
    message = "bbbb"

    setTimeout(() => {
      console.log("10s后的setTimeout")
    }, 0);

    console.log("Hello JavaScript")
    console.log("代码继续执行~~~")
    console.log("-------------")

  </script>

</body>

单线程-微任务和宏任务区别

promise里面的函数和写在外面的函数一样,都是马上就执行,没有放到事件队列中。但是如果这个promise有执行resolve或者reject时,回调函数then和catch里面的代码是会放到队列里面。

定时器的代码会被放到宏任务(macrotask)中,then里面的代码会放到微任务(microtask)当中。

宏任务和微任务的划分是规定好了的,不是我们来选择的。

微任务的作用就是希望比宏任务先执行,所以微任务比宏任务先执行。

  console.log("script start")

    // function bar() {
    //   console.log("bar function")
    // }

    // function foo() {
    //   console.log("foo function")
    //   bar()
    // }
    // foo()

    // 定时器
    setTimeout(() => {
      console.log("setTimeout0")
    }, 0)
    setTimeout(() => {
      console.log("setTimeout1")
    }, 0)

    // Promise中的then的回调也会被添加到队列中
    console.log("1111111")
    new Promise((resolve, reject) => {
      console.log("2222222")
      console.log("-------1")
      console.log("-------2")
      resolve()
      console.log("-------3")
    }).then(res => {
      console.log("then传入的回调: res", res)
    })
    console.log("3333333")

    console.log("script end")

代码执行顺序-面试题一(重要)

注意就是普通的函数和promise回调函数都是直接放到执行上下文中执行的,promise的resolve和reject回调的then和catch里面的代码是放到微任务中执行。

微任务比宏任务先执行

最好的办法就是把宏任务队列和微任务队列的执行顺序画出来

 

 微任务如果里面有微任务的话,宏任务就会一直延后才会执行,不管微任务是否早就执行完成。执行到宏任务时如果创建里面微任务(已经执行到宏任务了,只能说微任务已经结束了),等这一个宏任务完成之后,就立马回去执行微任务

 最终执行顺序:

 console.log("script start")

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

    console.log("script end")

代码执行顺序-await代码

then如果是在宏任务中执行的,那包裹这个then的宏任务会先执行后再执行then微任务。

定时器还分时间的,时间短的先进宏任务。

 await还没拿到结果前,这个作用域内的后续代码直接不执行,跳过。并且在同一个作用域的await后面的代码是相当于then里面的代码,都是放到微任务的。

 console.log("script start")

    function requestData(url) {
      console.log("requestData")
      return new Promise((resolve) => {
        setTimeout(() => {
          console.log("setTimeout")
          resolve(url)
        }, 2000);
      })
    }

    // 2.await/async
    async function getData() {
      console.log("getData start")
      const res = await requestData("why")
      
      console.log("then1-res:", res)
      console.log("getData end")
    }

    getData()
    
    console.log("script end")

    // script start
    // getData start
    // requestData
    // script end

    // setTimeout

    // then1-res: why
    // getData end

代码执行顺序-面试题二

下图执行到async2()的时候会去调用asunc2的函数,虽然只打印的是async2,但是有隐藏的return undefined,相当于resolve(undefined),而console.log('async1 end')这行会被加入到微任务里面,因为相当于被放到then里面了。

 注意:await async2()的代码会和console.log('async1 start')一样当做普通代码执行,而await async2()是个函数,要找到那个函数然后跳到这个函数里面执行里面的代码。

 

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

异常处理-默认错误处理

如果是浏览器报错,那出错的源码那行之后的代码都不会执行了。所以很危险。

解决办法之一是主动抛出异常,使用throw关键字。但是throw后面的代码也不会执行,好处是可以抛出你自定义的问题描述。

 // 1.遇到一个错误, 造成后续的代码全部不能执行
    // function foo() {
    //   "abc".filter()

    //   console.log("第15行代码")
    //   console.log("-------")
    // }

    // foo()
    // console.log("+++++++++")

    // const btn = document.querySelector("button")
    // btn.onclick = function() {
    //   console.log("监听btn的点击")
    // }

    // 2.自己封装一些工具
    function sum(num1, num2) {
      if (typeof num1 !== "number") {
        throw "type error: num1传入的类型有问题, 必须是number类型"
      }

      if (typeof num2 !== "number") {
        throw "type error: num2传入的类型有问题, 必须是number类型"
      }

      return num1 + num2
    }

    // 李四调用
    const result = sum(123, 321)

异常处理-throw抛出异常

抛出异常的信息是一句话的话没什么效果,一般会使用对象类型来抛出去,能显示的内容跟多。当然也可以自定义class来写错误信息。

系统也有已经写好的Error的类可以直接使用。 

  class HYError {
      constructor(message, code) {
        this.errMessage = message
        this.errCode = code
      }
    }

    // throw抛出一个异常
    // 1.函数中的代码遇到throw之后, 后续的代码都不会执行
    // 2.throw抛出一个具体的错误信息
    function foo() {
      console.log("foo function1")
      // 1.number/string/boolean
      // throw "反正就是一个错误"

      // 2.抛出一个对象
      // throw { errMessage: "我是错误信息", errCode: -1001 }
      // throw new HYError("错误信息", -1001)

      // 3.Error类: 错误函数的调用栈以及位置信息
      throw new Error("我是错误信息")

      console.log("foo function2")
      console.log("foo function3")
      console.log("foo function4")
    }

    function bar() {
      foo()
    }

    bar()

异常处理-异常的捕获方式

为什么一旦出现报错,后续代码都不执行了呢?

是因为从报错的地方层层向上报错,最终会在浏览器中抛出错误,所以后续代码就中断执行了,如果我们又写捕获处理错误的代码,那后续代码就可以继续执行。

finally是一定会执行的

 function foo() {
      console.log("foo function1")
      // throw new Error("我是错误信息")
      console.log("foo function2")
      console.log("foo function3")
      console.log("foo function4")
    }

    function test() {
      // 自己捕获了异常的话, 那么异常就不会传递给浏览器, 那么后续的代码可以正常执行
      try {
        foo()
        console.log("try后续的代码")
      } catch(error) {
        console.log("catch中的代码")
        // console.log(error)
      } finally {
        console.log("finally代码")
      }
    }

    function bar() {
      test()
    }

    bar()

    console.log("--------")

写了try catch捕获异常后的结果,后续代码可以正常执行。

猜你喜欢

转载自blog.csdn.net/weixin_56663198/article/details/131728637
今日推荐