ajax、axios、XHR --- 发送网络请求
1. Ajax
2. axios
1. 下载
1. 标签引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
2. npm下载
npm i -g axios
2. API 发送
axios.get("https://hmajax.itheima.net/api/city?pname=河北省")
.then(result => {
// result 就是服务器返回的数据
if (result.status === 200){
console.log(result)
}
})
.catch((e) => {
console.log("请求出错了", e)
})
3. 配置项 发送
axios({
url: 'https://hmajax.itheima.net/api/city',
method: 'get', // 默认是get请求,可以省略,并且不区分大小写
params: { // 发送GET请求是params参数配置项
pname: "山东省"
}
})
.then(result => {
console.log(result)
})
.catch((e) => {
console.log("请求出错了", e)
});
4. 请求头配置
axios({
url: "",
headers:{
Authorization: `Bearer ${localStorage.getItem('token')}`
}
})
5. 请求拦截器
统一设置公共 请求参数,每次发送请求前,要先按照请求拦截器中的配置配置好axios,再发送请求
// 添加请求拦截器
axios.interceptors.request.use(function (config){
// 每次发送请求,携带令牌在请求头中
const token = location.getItem('token')
token && (config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`)
return config
},function (error){
// 请求错误后的逻辑
return Promise.reject(error)
})
6. 响应拦截器
// 添加请求拦截器,对身份验证失败,统一判断并处理
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数
// 可以将响应数据对象在这里取出来,将来axios请求成功的result对象就是这里取出来的数据对象
const result = response.data
return result
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数
// ?.response 是可选链式操作符,当 error 有值才 调用.response
if (error?.response?.status === 401){
alert('登录状态过期,请重新登录')
localStorage.clear()
location.href = "../login/index.html"
}
return Promise.reject(error)
})
7. GET 查询参数
获取数据
1. ?携带参数
axios.get("https://hmajax.itheima.net/api/city?pname=河北省")
.then(result => {
// result 就是服务器返回的数据
if (result.status === 200){
console.log(result)
}
})
.catch((e) => {
console.log("请求出错了", e)
})
2. params 参数对象
参数形式携带查询参数发送GET请求
axios.get("https://hmajax.itheima.net/api/city", {
params: {
pname: "山东省"
}
})
.then(result => {
// result 就是服务器返回的数据
if (result.status === 200) {
console.log(result)
}
})
.catch((e) => {
console.log("请求出错了", e)
})
8. POST
将参数携带在 请求体 中,发送给后端
1. 请求体参数
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
将参数携带在 请求体 中,发送给后端
axios({
url: 'http://127.0.0.1:8080/api/register',
method: 'post',
data: { // 发送POST请求是data参数配置项
username: 'Fred123',
password: 'Flintstone'
}
})
9. PUT
将参数携带在 请求体 中,发送给后端
axios({
url: 'http://127.0.0.1:8080/api/register',
method: 'put',
data: {
id:1,
nickname: 'Fred123',
}
})
10. DELETE
将参数携带在 请求体 中,发送给后端
axios({
url: 'http://127.0.0.1:8080/api/register',
method: 'delete',
data: {
id: ,
}
})
3. axios 底层原理
2.1 XMLHttpRequest 对象
**定义: **XMLHttpRequest (XHR)对象
用于与服务器交互。通过 XMLHttpRequest
可以在不刷新页面的情况下请求特定 URL
,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。是 Ajax 的底层实现。
1. 基本使用
const xhr = new XMLHttpRequest() // 实例化对象
xhr.open("get", "http://www.baidu.com") // 配置请求方法和请求路径
xhr.addEventListener("loadend", () => { // 监听 loadend 事件,并绑定成功的回调函数
console.log(xhr.response) // 得到的是JSON字符串
})
xhr.send() // 发送请求
2. GET 查询参数
const xhr = new XMLHttpRequest()
// 1. 将对象转成 a=1&b=2 形式的查询参数
const paramsObj = new URLSearchParams({
a: 1,
b: 2
})
const queryString = paramsObj.toString()
// 2. 调用 XMLHttpRequest 发送请求
xhr.open("get", "http://www.baidu.com?" + queryString) // 使用 ? 拼接查询参数
xhr.addEventListener("loadend", () => {
console.log(xhr.response)
})
xhr.send()
3. POST 请求体参数
const xhr = new XMLHttpRequest()
xhr.open("get", "http://www.baidu.com")
xhr.addEventListener("loadend", () => {
console.log(xhr.response)
})
// 1. 设置请求头,告诉服务器,我传递的是 JSON 字符串
xhr.setRequestHeader("Content-Type","application/json")
// 2. 将数据转成 JSON 字符串
const userObj = {
username: "xiaoming",
password: "12345678"
}
const userStr = JSON.stringify(userObj)
xhr.send(userStr) // 3. 发送请求,携带 JSON 字符串
2.2 Promise 对象
Promise对象
用于表示一个异步操作的最终完成(或失败)及其结果
1. 基本使用
// 使用 Promise 管理异步任务
const p = new Promise((resolve,reject)=>{
// 在 Promise 对象创建时,内部的回调函数代码会立刻被执行
// 执行异步任务并传递结果,使用 setTimeout 模拟发送请求的过程
setTimeout(()=>{
resolve("执行成功了,这是结果") // 成功调用: 状态变为 fulfilled 状态-已兑现,并触发then()执行
},2000)
setTimeout(()=>{
reject(new Error("执行失败了,这是结果")) // 失败调用: 状态变为 rejected 状态-已拒绝,并触发catch()执行
},2000)
})
// 接收结果
p.then(result=>{
console.log(result)
}).catch(error=>{
console.log(error)
})
2. 三种状态
1. pending (待定)
刚初始化的时候就是 pending 状态
new Promise()
2. fulfilled(已兑现)
当发现状态标记为 fulfilled 状态时,会自动调用 then() 中注册的回调函数,并将 结果 传递给 then() 的回调函数
resolve()
3. rejected(已拒绝)
当发现状态标记为 rejected状态时,会自动调用 catch() 中注册的回调函数,并将 结果 传递给 catch() 的回调函数
reject()
Peomise 对象一旦是 兑现/拒绝 状态就无法再被改变
3. 使用 Promise 对象管理 XHR 对象的异步任务
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("GET", "http://hmajax.itheima.net/api/province")
xhr.addEventListener("loadend", () => {
if (200 <= xhr.status < 300) {
const resultObj = JSON.parse(xhr.response)
resultObj.status = xhr.status // 可以给响应对象添加一下属性
resolve(resultObj)
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
p.then(result=>{
console.log(result)
}).catch(error=>{
console.log(error)
})
2.3 封装模拟 axios
// 模拟 axios
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
// 对于查询参数的处理
if (config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryStirng = paramsObj.toString()
config.url += `?${queryStirng}`
}
xhr.open(config.method || "GET", config.url)
xhr.addEventListener("loadend", () => {
if (200 <= xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
// 处理携带请求体参数
if (config.data) {
xhr.setRequestHeader("Content-Type", "application/json")
xhr.send(JSON.stringify(config.data))
} else {
xhr.send()
}
})
}
// 发送普通 get 请求
myAxios({
url: "http://hmajax.itheima.net/api/province"
}).then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
// 携带查询参数
myAxios({
url: "http://hmajax.itheima.net/api/area",
params: {
pname: "北京",
cname: "北京市"
}
}).then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
// 发送POST请求,并携带请求体参数
myAxios({
url: "http://hmajax.itheima.net/api/register",
method:"post",
data: {
username: "xiaoming",
password: "xiaoming123"
}
}).then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
2.4 同步和异步
1. 同步
**同步: ** 逐行执行, 需原地等待结果后,才继续向下执行
2. 异步
**异步: ** 调用后耗时,不阻塞代码继续执行(不必原地等待), 在将来完成后触发一个回调函数
3. 回调函数地狱
在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱,
axios({url: "http://hmajax.itheima.net/api/province"}).then(result => {
const pname = result.data.list[0]
document.querySelector(".province").innerHTML = pname
axios({url: "http://hmajax.itheima.net/api/city", params: {pname}}).then(result => {
const cname = result.data.list[0]
document.querySelector(".city").innerHTML = cname
axios({url: "http://hmajax.itheima.net/api/area", params: {pname, cname}}).then(result => {
const aname = result.data.list[0]
document.querySelector(".area").innerHTML = aname
})
})
})
**缺点: **
- 可读性差
- 异常无法捕获
- 耦合性严重,牵一发而动全身
4. Promise 链式调用
利用 then() 方法会返回一个新生成的 Promise 对象的特性,继续串联下一环任务,直到结束
1. 解决回调函数地狱的问题
const p = new Promise((resolve, reject) => {
// 模拟异步请求
setTimeout(() => {
resolve("北京")
}, 2000)
})
// 返回一个新的 Promise 对象
const p2 = p.then(result => {
console.log(result)
// 模拟异步请求
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + "---北京市")
}, 2000)
})
})
const p3 = p2.then(result => {
console.log(result)
// 模拟异步请求
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + "---昌平区")
}, 2000)
})
})
p3.then(result => {
console.log(result)
})
2. 修改上面回调函数地狱中的代码,解决回调函数地狱问题
// 其他 Promise 对象中如果使用到了第一个对象中的变量 则需要声明全局变量
let pname
axios({url: "http://hmajax.itheima.net/api/province"}).then(result => {
// 赋值给全局变量
pname = result.data.list[0]
console.log("省/直辖市名>>>",pname)
// 返回一个新的 Promise 对象
return axios({url: "http://hmajax.itheima.net/api/city", params: {pname}})
}).then(result => {
const cname = result.data.list[0]
console.log("城市名>>>",cname)
// 返回一个新的 Promise 对象
return axios({url: "http://hmajax.itheima.net/api/area", params: {pname,cname}})
}).then(result=>{
const aname = result.data.list[0]
console.log("地区名>>>",aname)
})
5. async / await
async 函数是使用 async 关键字声明的函数, async 函数 是 AsyncFunction 构造函数的实例, 并且其中允许使用 await 关键字。 async 和 await 关键字 让我们可以使用一种更简介的方式写出基于 Promise 的异步行为,而无需可以的链式调用 Promise
1. 解决回调函数地狱的问题
// 使用 async 关键字 修饰当前函数
async function getDefaultArea() {
// await 关键字修饰的 Promise 异步任务,暂停当前整个函数的执行,等待这个异步任务执行结束得到结果,再继续往下执行
const pObj = await axios({url: "http://hmajax.itheima.net/api/province"})
const pname = pObj.data.list[0]
const cObj = await axios({url: "http://hmajax.itheima.net/api/city", params: {pname}})
const cname = cObj.data.list[0]
const aObj = await axios({url: "http://hmajax.itheima.net/api/area", params: {pname, cname}})
const aname = aObj.data.list[0]
// 赋值到页面中
document.querySelector(".province").innerHTML = pname
document.querySelector(".city").innerHTML = cname
document.querySelector(".area").innerHTML = aname
}
getDefaultArea()
**2.使用 try ... catch() ... 捕获 async 函数的异常 **
async function getDefaultArea() {
// 使用 try 将有可能发生错误的代码包裹
try {
const pObj = await axios({url: "http://hmajax.itheima.net/api/province"})
const pname = pObj.data.list[0]
const cObj = await axios({url: "http://hmajax.itheima.net/api/city", params: {pname}})
const cname = cObj.data.list[0]
const aObj = await axios({url: "http://hmajax.itheima.net/api/area", params: {pname, cname}})
const aname = aObj.data.list[0]
document.querySelector(".province").innerHTML = pname
document.querySelector(".city").innerHTML = cname
document.querySelector(".area").innerHTML = aname
} catch (error) {
// 如果 try 中的语句发生错误,会立即到跳转到 catch() 中执行
}
}
getDefaultArea()
2.5 事件循环 (EventLoop)
1. 概念
**概念: ** JavaScript 中有一个基于事件循环的并发模型, 事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其他语言中的模型截然不同
**原因: ** JavaScript 是单线程的(某一时刻只能执行一行代码), 为了让耗时任务不阻塞其他代码运行,故设计了事件循环模型
**定义: ** 执行代码和收集异步任务的模型,在调用栈空闲时,反复调用任务队列里可执行回调函数的机制,就叫做事件循环
2. 执行过程
分析下面代码的执行过程
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
setTimeout(() => {
console.log(4)
}, 2000)
console.log(5)
// 执行结果:
1
3
5
2
4
JS 引擎从上之下一行一行读取代码,当遇到 console.log(1) 时,会将代码放到调用栈,然后继续往下读代码,与此同时调用栈调用代码,会在控制台中输出1,然后出栈,当读第二行的时候是一个 setTimeout 的异步代码,JS 引擎会将代码放入浏览器中执行,浏览器会倒计时,如果倒计时结束了,会将其中的回调函数推入到内存中的任务队列中排队,同步代码放入调用栈,耗时的异步代码放入浏览器环境执行,一直到所有代码都读完以后调用栈空闲的时候,会一直读取任务队列中是否有可执行的代码,如果有就放入调用栈中执行,如果没有则一直读取
2.6 宏任务与微任务
ES6 之后引入了 Promise 对象,让 JS 引擎也可以发起异步任务
**异步任务分为: **
- 宏任务: 由浏览器环境执行的异步代码
- 微任务: 由JS引擎环境执行的异步任务
任务(代码) | 执行所在环境 |
---|---|
JS 脚本执行事件(script) | 浏览器 |
setTimeout/setInterval | 浏览器 |
AJAX请求完成事件 | 浏览器 |
用户交互事件等 | 浏览器 |
Promise对象.then() | JS引擎 |
Promise对象.then().catch() | JS引擎 |
宏任务与微任务调度流程
JS 引擎从上之下一行一行读取代码,script脚本是第一个宏任务,开始读取,当遇到 console.log(1) 时,会将代码放到调用栈,然后继续往下读代码,与此同时调用栈调用代码,会在控制台中输出1,然后出栈,当读第二行的时候是一个 setTimeout 的宏任务异步代码,JS 引擎会将代码放入浏览器中执行,浏览器会倒计时,如果倒计时结束了,会将其中的回调函数推入到内存中的宏任务任务队列中排队,同步代码放入调用栈,耗时的异步代码放入浏览器环境执行,Promise对象的then()是微任务,会放到微任务队列中排队,一直到所有代码都读完以后调用栈空闲的时候,会一直读取任务队列中是否有可执行的代码,优先调度微任务队列(更接近JS引擎)中的任务一个一个放入调用栈中执行,微任务队列中正在等待的任务全部执行结束后,调度宏任务队列,如果有就放入调用栈中执行,如果没有则一直先微任务队列,再宏任务队列反复读取
**面试题: **
console.log(1)
setTimeout(() => {
console.log(2)
const p = new Promise(resolve => resolve(3))
p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {
setTimeout(() => {
console.log(4)
}, 0)
resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)
2.7 Promise.all 静态方法
合并多个 Promise 对象,等待所有任务同时成功(或某一个任务失败),然后做后续逻辑
const p1 = axios({url: "http://hmajax.itheima.net/api/weather",params:{city:"110100"}})
const p2 = axios({url: "http://hmajax.itheima.net/api/weather",params:{city:"310100"}})
const p3 = axios({url: "http://hmajax.itheima.net/api/weather",params:{city:"440100"}})
const p4 = axios({url: "http://hmajax.itheima.net/api/weather",params:{city:"440300"}})
const p = Promise.all([p1, p2, p3, p4])
p.then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
// 优化
const cityArr = ["110100", "310100", "440100", "440300"]
const pArr = []
for (let i in cityArr) {
pArr.push(axios({url: "http://hmajax.itheima.net/api/weather", params: {city: cityArr[i]}}))
}
const p = Promise.all(pArr)
// 所有的任务对象全部成功,则调用.then()
p.then(result => {
// 数组中的结果数据和定义时的 Promise 顺序是一致的
console.log(result)
}).catch(error => {
// 如果其中一个失败了,则调用.catch()
console.log(error)
})