独立开发者系列(37)——理解async和await

1.理解同步与异步

同步就是执行某个任务A-B-C-D,就是严格按照顺序执行,可以理解为正常的代码逻辑,如果运行的代码都不是很消耗时间的情况系啊,同步可以减少编程的复杂度。但是对于请求远程服务结果的这种服务,如果同步存在一个巨大的问题,如果你在B步骤是向某个很消耗时间的网页发出请求,C/D和后面的步骤全部卡住,因为必须等B结束才能执行后面的逻辑。这个叫同步阻塞式编程。当网站三五个人访问的时候,其实也没什么问题,但是当页面和访问的人数变多了后,会发现变得不可忍受。

异步,这个时候,系统为了提高响应性能,在B过程里面请求远程URL,直接将B过程挂起,继续执行C/D…等后面的逻辑,这样哪怕B响应时间很长,但是只要B响应过来,页面已经加载的差不多了,这就是异步非阻塞式编程的思路。

很明显,异步非阻塞效率远远高于同步的执行效率,流程越多的时候,这个差距越明显。所以在最早期的JS编程里面,直接就是默认异步请求网络连接的模式。

2使用XMLHttpRequest 对象


最早期的和网络通讯的接口这个也就是我们浏览器里面最常抓取的XHR数据包,我们之前最常用的ajax其实就是也是对XHR的进一步封装,封装了请求状态码之类的。$.ajax() 引入了jquery库之后,我们可以很轻松的读取抓取远程链接。

但是XHR比较繁琐,早期我们编程就感觉很困难,主要表现:

​ 一.需要设置诸如 open 方法中的请求方法(GETPOST 等)和请求 URL,还要设置 onreadystatechange 事件来监听请求状态的变化,并在不同的状态下进行相应的处理

​ 二.请求拿到结果,还要进行必须通过检查 readyState 的不同值来确定请求的阶段,例如 0 表示未初始化,1 表示已打开,2 表示已发送,3 表示正在接收,4 表示完成。

​ 三.从响应中获取数据需要根据响应的类型(如 responseTextresponseXML 等)进行不同的处理,还要处理不同的响应类型

​ 四:需要检查 status 属性来确定是否出现错误,并且不同的错误状态码需要单独处理

​ 五: 同步和异步代码是混编在一起,我们很难区别哪些是同步请求,哪些是异步请求,当网络应用复杂后,有些场景需要同步请求,这将导致代码可阅读性更差

  <script>
   //写一个最简单的XHR模块
    function makeRequest() {
    //初始化XHR的请求
      let xhr = new XMLHttpRequest();
      // 设置请求方法和 URL
      xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
      // 设置请求头
      xhr.setRequestHeader('Content-Type', 'application/json');
      // 监听请求状态变化
      xhr.onreadystatechange = function () {
        //XHR本身还有完成状态,才能继续进行下一步
        if (xhr.readyState === 4) {
           //自身完成的情况下,必须拿到请求状态码是200
          if (xhr.status === 200) {
            let response = JSON.parse(xhr.responseText);
            console.log(response);;
          } else {
            //请求失败
            document.getElementById('result').innerHTML = `<p>请求失败,状态码: ${xhr.status}</p>`;
          }
        }
      };
      // 配置好了上面的参数,然后还要send()才算开始请求
      xhr.send();
    }
  </script>

​ 从这个简单的例子我们可以看到,我们发送一个get请求,都需要完成一堆的代码,所以jquery的封装简化了一部分逻辑,但是本身使用上确实不是很方便。如果碰上是同步请求,混淆在异步请求里面,我们对代码的阅读能力更加的头疼

3.Fetch API

随着网络技术的发展 后面出现了一个新的请求方式Fetch API,主要是为了简化XHR的一些繁琐程度。同样我们阅读下面的源码,可以很明显的看到哪个是同步请求,哪个是异步请求:

<script>
//网络的同步请求,在XHR里面要去找对应的true和false 可以看到大幅度简化了请求和优化了报错
fetch('https://xxx.com/todos/1').then(response => {
    //直接拿到response数据 甚至直接将响应解析为 JSON 格式
    if (response.ok) {
      return response.json(); 
    }
    throw new Error('网络请求失败');
  }).then(data => {
    //直接可以在这里面处理数据逻辑 比XHR明显清晰
    console.log(data);
  }).catch(error => {
    //报错也大幅度简化  可以直接打印出来
    console.error(error);
  });
</script>

4.理解promise概念

英文单词的意思就是承诺。考虑到网络请求越来越复杂,于是js里面引入了promise的概念。只要加入了promise,用我们日常的话意思是,我现在要外出一趟,待会我一定会回来,你记得等我的消息。这个有什么用呢?其实只是一种标记,表示我一定会回来的,但是这个标记进行了特殊处理。在JS的编程里面,除了延时,基本就是网络请求需要异步,而这个有了promise概念。看下面的代码段,更直观理解promise

//我们定义俩个promise异步对象
function getData1() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('data1');
    }, 1000);
  });
}
function getData2() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('data2');
    }, 1500);
  });
}
//异步对象可以一直使用then连接 将前面返回到后面的结果
getData1().then(data1 => {
    //执行第一次就拿到data1的结果,变成参数继续请求后面的
    return getData2();
  }).then(data2 => {
    //then里面的结果全部都可以当成参数使用,特别适合需要请求多个API,拿到不同数据,最后组合使用的场景
    console.log(data1, data2);
  }).catch(error => {
    console.error(error);
  });

如果我们不使用promise的编程思路,代码块是互相嵌套的:

function getData1(callback) {
  setTimeout(() => {
    callback('data1');
  }, 1000);
}
function getData2(callback) {
  setTimeout(() => {
    callback('data2');
  }, 1500);
}
//代码块互相嵌套 多几层的时候,非常混乱
getData1(data1 => {
  getData2(data2 => {
    console.log(data1, data2);
  });
});

对比下,可以看出俩者的区别:

promise避免了函数反复的嵌套调用(多层嵌套会导致函数理解起来异常复杂),可以通过then进行链式操作,可以很直观的看出错误的异常。而在传统的编程思路里面

5. async和await的理解

前面我们理解了远程网络的请求 同时理解了promise的用法机制。我们知道我们大部分的网络请求都是异步的,但是我们在编程的时候,习惯性的会按照同步编程的思路来写代码,这个时候 我们需要区分哪些是明确的异步操作。这样无论编程还是理解都大有好处,要不然密密麻麻代码一大片,结果我们连哪些是同步哪些是异步逻辑都分不清,这个时候对维护代码非常不利。

于是为了优化这种代码层面的理解,ES6定义了一个关键字,就是async 只要这个关键字出现,我们就知道了这是一个异步函数。而只要是

async 函数返回一个 Promise 对象。如果函数内部的异步操作成功完成,Promise 就会被解决(resolved)并返回结果;如果发生错误,Promise 就会被拒绝(rejected)并抛出错误。

而当我们在一个明确了是异步的函数里面,遇到真正需要远程请求的数据的时候,直接使用await标记 表示这个是async异步执行的结果,需要等待这个地方的执行结果,才能返回数据。网站请求远程数据,立刻标记下需要等待远程的网络请求,否则就不会返回结果。这种场景特别适合,当A-B-C-D逻辑,C逻辑一定依赖B异步的执行结果才能继续,这个时候,我们把B-C封装成一个异步,A/D因为不依赖,可以直接先完成,这样适应更加复杂的网络场景。

结合上面我们知道promise可以进行的链式操作。我们就可以使用then来实现

//我们定义一个异步获取数据的方法,看到关键字的async的时候,我们就已经知道这是一个异步的执行
async function getData() {
  try {
     //这个异步方法的执行,首先必须得远程请求获取数据,要不然其他代码都不用执行
    const response = await fetch('https://example.com/data');
      //获取数据进行解析的前提 必须是获取数据 
    const data = await response.json();
      //不能直接返回,因为必须返回json解开了的格式数据
    return data;
  } catch (error) {
      //中途失败就报错返回
    console.error('发生错误:', error);
  }
}
//async就是一个promise对象,执行使用then获取到对应的数据
getData().then(result => {
  console.log(result);
});
  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大梁来了

千山万水总是情,打赏一块行不行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值