十五. async_ await_ 线程

71 阅读5分钟

十五. async_ await_ 线程

15.1. 认识异步函数

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

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

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

 async function foo1() { }
 const foo2 = async () => { }
 const foo3 = async function () { }
 class Foo {
     async bar() { }
 }

15.2. async的执行流程

  1. 异步函数的内部代码执行过程和普通的函数是一致的(async函数内部没有await),默认情况下也是会被同步执行。

     // 输出顺序是:1 3 2
     async function foo() {
         console.log(3);
     }
     console.log(1);
     foo()
     console.log(2);
    

15.3. 异步和普通函数的区别

  1. 异步函数有返回值时,和普通函数会有区别:

    • 情况一:异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中;
    • 情况二:如果我们的异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定;
    • 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;
     async function foo() {
         console.log(1);
         console.log('中间代码');
         console.log(100);
         // 1. 返回一个值(数字/字符串/undefined等)
         // 2. 返回thenable
         // return {
         //     then:function(resolve, reject){
         //         resolve('实现了thenable')
         //     }
         // }
         // 3. 返回Promise
         return new Promise((resolve, reject) => {
             setTimeout(() => {
                 resolve('返回了一个Promise')
             }, 1000);
         })
     }
     ​
     foo().then(res => {
         console.log("promise then function exec: " + res);
     })
    
  2. 如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;

     async function foo() {
         console.log(1);
         throw new Error('抛出异常')
     }
     foo().catch(err => {
         console.log('err: ' + err); // err: Error: 抛出异常
     })
     ​
     console.log('后续代码会执行~~~');
    
  3. async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。

15.4. async中使用await关键字

await关键字有什么特点呢?

  • 通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;

  • 那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;

     function requestData(url) {
         return new Promise((resolve, reject) => { 
             setTimeout(() => {
                 resolve(url)
             }, 1000);
         })
     }
     ​
     async function foo(){
         // 通常await后面跟的是一个Promise,等Promise回调resolve(结果)时,会得到一个返回值(即resolve的参数),然后把这个返回值结果给res,然后执行then()
         // 可以用debugger验证,这里和generator有点不一样,具体如下
         const res= await requestData('why')  
         debugger
         // await后面的代码要等到执行resolve()的时候才会依次执行,
         // 我们知道resolve()执行时,是要执行then里面的回调,但这里没有写then方法,而是直接执行后面的代码了,
         // 实际上后面的代码就是在then里面执行的,只是内部实现了,我们看不到而已;整体思路和generator思路一样
         console.log(res); 
     }
     foo()
    
  • 如果await后面是一个普通的值,那么会直接返回这个值;

     async function foo(){ 
         const res= await 123  
         console.log(res); // 123
     }
     foo()
    
  • 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;

     function requestData(url) {
         return new Promise((resolve, reject) => {
             setTimeout(() => {
                 resolve(url)
             }, 1000);
         })
     }
     ​
     async function foo() {
         const res = await {
             then: function (resolve, reject) {
                 resolve(111)
             }
         }
         console.log(res); // 111
     }
     foo()
    
  • 如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值;

     function requestData(url) {
         return new Promise((resolve, reject) => {
             setTimeout(() => {
                 reject(url)
             }, 1000);
         })
     }
     ​
     async function foo() {
         await requestData(222)
     }
     foo().catch(err => {
         console.log(err); // 222
     })
    

15.5. 进程和线程

线程和进程是操作系统中的两个概念:

  • 进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式;
  • 线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中;

听起来很抽象,这里还是给出我的解释:

  • 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
  • 线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程;
  • 所以我们也可以说进程是线程的容器;

image-20220321192508661

操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?

  • 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
  • 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
  • 对于用户来说是感受不到这种快速的切换的;

我们经常会说JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。

  • 浏览器是一个进程吗,它里面只有一个线程吗?

    • 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页 面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
    • 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;

JavaScript的代码执行是在一个单独的线程中执行的:

  • 这就意味着JavaScript的代码,在同一个时刻只能做一件事;
  • 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;

所以真正耗时的操作,实际上并不是由JavaScript线程在执行的:

  • 浏览器的每个进程是多线程的,那么浏览器的其他线程可以来完成这个耗时的操作;
  • 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;