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
方法中的请求方法(GET
、POST
等)和请求 URL,还要设置 onreadystatechange
事件来监听请求状态的变化,并在不同的状态下进行相应的处理
二.请求拿到结果,还要进行必须通过检查 readyState
的不同值来确定请求的阶段,例如 0
表示未初始化,1
表示已打开,2
表示已发送,3
表示正在接收,4
表示完成。
三.从响应中获取数据需要根据响应的类型(如 responseText
、responseXML
等)进行不同的处理,还要处理不同的响应类型
四:需要检查 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);
});