AJAX 是异步的 JavaScript 和 XML (Asynchronous JavaScript And XML)的简称
AJAX可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据
// 创建网络请求的AJAX对象(使用XMLHttpRequest)
const xhr = new XMLHttpRequest()
// 配置网络请求(通过open方法)
// 参数1 - 交互方式 - 大小写不区分
// 参数2 - URL请求地址
xhr.open('get', 'https://httpbin.org/get?name=Klaus&age=23')
// 监听XMLHttpRequest对象状态的变化,或者监听onload事件(请求完成时触发)
// 因为网络请求是异步的,在接收到数据后,会放入对应的宏任务队列中等待浏览器主线程调度
// 所以需要在对应的事件函数中处理对应的事件逻辑
xhr.addEventListener('readystatechange', () => {
// 如果请求完成, 就获取响应体并在console中打印
if (xhr.readyState === XMLHttpRequest.DONE) {
// 返回的内容是JSON格式字符串,所以在使用前需要先使用JSON进行解析
console.log(JSON.parse(xhr.response))
}
})
// 发送send网络请求
xhr.send()
XHR的状态
事实上,我们在一次网络请求中看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态
注意:
这个状态并非是HTTP的响应状态 ,而是记录的XMLHttpRequest对象的状态变化
http响应状态依旧需要通过响应行的status进行获取
const xhr = new XMLHttpRequest()
// xhr被创建的时候,xhr.readyState的默认值为0
console.log(xhr.readyState) // => 0
// xhr的配置和事件监听 没有绝对的先后顺序
xhr.addEventListener('readystatechange', () => {
console.log(xhr.readyState)
/*
=>
1
2
3
4
*/
// xhr.readyState的状态的值为 0 ~ 4
// 直接使用数字不合适也不容易维护和阅读,所以每一个状态码 都对应XMLHttpRequest上的一个常量
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log(xhr.response)
}
})
xhr.open('get', 'https://httpbin.org/get?name=Klaus&age=23')
xhr.send()
同步请求
默认情况下,XHR的请求是异步的,也就是事件会单独交给网络请求线程来进行执行,拿到对应的结果后,会存放到宏任务队列中等待处理
但是我们也可以设置xhr.open方法的第三个参数,将该参数设置为false的时候就表示当前XHR请求为同步请求
该参数的默认值为true,即XHR请求的默认行为是异步请求
const xhr = new XMLHttpRequest()
xhr.open('get', 'https://httpbin.org/get?name=Klaus&age=23', false)
xhr.send()
// 因为open的第三个参数设置为了false
// 那么就意味着xhr的send方法发送的网络请求变成了同步模式
// 会阻塞send方法后边的代码的正常执行
console.log(xhr.response)
其它事件监听
事件 | 功能 |
---|---|
loadstart | 请求开始时触发 |
progress | 1. 请求正在加载中 2. 可以通过 xhr.responseText 读取当前已经接收到的部分数据 |
abort | 调用 xhr.abort() 取消了请求 |
error | 当请求遇到网络错误,如网络中断、跨域错误等情况时,会触发该事件 不会发生诸如 404 这类的 HTTP 错误 因为 HTTP 响应代码 404 不是网络错误,而是一个合法的 HTTP 响应表示资源未找到,这会导致 load 事件的触发而不是 error 事件 |
load | 当请求成功完成时触发,无论响应的 HTTP 状态码是什么,只要服务器有响应就会触发 load |
timeout | 如果设定了超时时间,并且请求在该时间内没有完成,则触发 timeout 事件默认超时时间为0,即不设置超时时间,直到浏览器主动断开对应的链接 |
loadend | 在请求结束时触发,不管是通过 load , error , timeout , 或 abort 事件结束的,loadend 都会作为最后一个被触发的事件。 |
每个请求都从触发loadstart事件开始
接下来,通常每隔50毫秒左右触发一次progress事件
然后触发load、error、abort或timeout事件中的一个
最后以触发loadend事件结束
const xhr = new XMLHttpRequest()
xhr.open('get', 'https://httpbin.org/get?name=Klaus&age=23')
// onload 事件 会在 xhr.readyState === 4 的时候 被触发
xhr.addEventListener('load', () => console.log(xhr.response))
xhr.send()
const xhr = new XMLHttpRequest()
xhr.responseType = 'json'
// typeof e -> ProgressEvent
xhr.addEventListener('progress', e => {
console.log(e.loaded) // => 已经接收的字节数
console.log(e.total) // => 根据Content-Length响应头部确定的预期字节数
console.log(e.lengthComputable) // => 一个布尔值 表示是否加载完全
// 有了上述信息后,调用者可以自主决定如何展示对应的加载进度信息
})
xhr.open('get', 'http://www.httpbin.org/get?name=Klaus&age=23')
xhr.send()
响应数据和响应类型
发送了请求后,我们可以通过response属性来获取对应的结果
- XMLHttpRequest response 属性返回响应的正文内容
- 默认情况下 response属性的值是字符串的
- 因为默认情况下responseType的值为空字符串,当responseType的值为空字符串的时候,会被解析为默认值 text
- 所以我们可以通过修改responseType的值 来改变response对应值的类型
const xhr = new XMLHttpRequest()
xhr.open('get', 'https://httpbin.org/get?name=Klaus&age=23')
xhr.addEventListener('load', () => console.log(xhr.response))
// responseType: '' | 'text' | 'xml' | ’json‘
xhr.responseType = ''
// 上行代码等价于
// xhr.responseType = 'text'
xhr.send()
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://www.example.com/text/xml')
xhr.addEventListener('load', () => {
// response 返回的数据格式 (可以解析为普通文件 或 json)
console.log(xhr.response)
// 返回响应体对应的 text/plain 格式的内容
console.log(xhr.responseText)
// 如果返回的类型为xml,可以通过responseXML 来获取对应的解析后的xml内容
console.log(xhr.responseXML)
})
xhr.responseType = 'xml'
xhr.send()
响应状态和状态码
XMLHttpRequest的state是用于记录xhr对象本身的状态变化,并非针对于HTTP的网络请求状态
如果我们希望获取HTTP响应的网络状态,可以通过status和statusText来获取
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://www.httpbin.org/foo')
// xhr请求发送成功的时候,且响应体全部接收完毕时候执行的回调
// ps: 注意:不管状态码是多少,只要发送请求成功都会被onload方法监听
// 即使当前请求的状态码是 404 500 或 其它
xhr.addEventListener('load', () => {
console.log(xhr.status, xhr.statusText) // 404 'NOT FOUND'
})
// xhr请求发送失败时候执行的回调
xhr.addEventListener('error', () => {
console.log('请求发送失败')
})
xhr.responseType = 'json'
xhr.send()
参数传递
常见的参数传递方式有如下几种格式:
- GET请求的query参数
- POST请求 x-www-form-urlencoded 格式
- POST请求 FormData 格式
- POST请求 JSON 格式
GET请求的query参数
const xhr = new XMLHttpRequest()
const searchParams = new URLSearchParams()
searchParams.append('name', 'Klaus')
searchParams.append('age', 23)
// get请求在传递数据的时候,需要将参数挂载为URL的query参数
// http://www.httpbin.org/get?name=Klaus&age=23
xhr.open('get', `http://www.httpbin.org/get?${searchParams.toString()}`)
xhr.addEventListener('load', () => {
console.log(xhr.response)
})
xhr.responseType = 'json'
xhr.send()
POST请求 x-www-form-urlencoded 格式
const xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.httpbin.org/post')
xhr.addEventListener('load', () => {
console.log(xhr.response)
})
xhr.responseType = 'json'
// 默认情况下,post请求方式中 请求体中的数据 是按照text/plain的方式进行传输和解析的
// 所以如果传递的数据是按照x-www-form-urlencoded进行传输的时候
// 需要手动设置请求头Content-type 为 application/x-www-form-urlencoded
// 请求头名 不区分大小写 不过请求数据编码类型一遍写为 Content-type
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
// post请求传递参数的时候
// 如果参数使用 x-www-form-urlencoded方式传递数据的时候
// 需要将对应的参数以urlencoded方式进行编码后 作为send方法的参数被传入
xhr.send(searchParams.toString())
POST请求 FormData 格式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>document</title>
</head>
<body>
<form id="form">
<input type="text" name="username">
<input type="password" name="password">
</form>
<button id="send">send</button>
<script>
const btnEl = document.getElementById('send')
btnEl.addEventListener('click', () => {
const xhr = new XMLHttpRequest()
const formEl = document.getElementById('form')
xhr.open('POST', 'http://www.example.com/postform')
xhr.addEventListener('load', () => {
console.log(xhr.response)
})
xhr.responseType = 'json'
// 如果要提交表单数据,可以使用FormData 结合表单元素 生成对应的formData实例
const formData = new FormData(formEl)
// 传递数据的时候 需要将对应的formData实例对象作为send方法的参数
// 如果浏览器发送传递的数据类型为formData的时候
// 浏览器会自动将Content-type设置为multipart/form-data
// 不需要我们手动进行设置
xhr.send(formData)
})
</script>
</body>
</html>
POST请求 JSON 格式
const xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.httpbin.org/post')
xhr.addEventListener('load', () => {
console.log(xhr.response)
})
xhr.responseType = 'json'
// 设置Content-typed的时候可以在编码后边指定文件的类型
// 如 application/json; charset=utf-8
// 如果不指定文件类型的时候,分号必须省略,且默认的文件编码格式为utf-8
xhr.setRequestHeader('Content-type', 'application/json')
// json格式数据 传递时候 需要将json格式对象转换为json格式字符串后
// 作为send方法的参数进行传递
xhr.send(JSON.stringify({name: 'Klaus', age: 23}))
其它补充
超时时间
在网络请求的过程中,为了避免过长的时间服务器无法返回数据,通常我们会为请求设置一个超时时间: timeout
当达到超时时间后依然没有获取到数据,那么这个请求会自动被取消掉
XHR的默认超时时间为0s,表示没有设置超时时间, 一般推荐设置为10s
const xhr = new XMLHttpRequest()
xhr.responseType = 'json'
// 设置超时时间
// 当一个请求超过3s依旧没有返回值的时候
// 浏览器会自动取消该次请求,即使后续服务器返回了对应的数据,浏览器也将不再进行任何的处理
xhr.timeout = 3000
xhr.onload = () => console.log(xhr.response)
// 监听XHR的超时事件
xhr.ontimeout = () => console.log('请求超时了')
// http://www.example.com/timeout 是一个6s后才会返回对应结果的接口
xhr.open('get', 'http://www.example.com/timeout')
xhr.send()
取消请求
const xhr = new XMLHttpRequest()
xhr.responseType = 'json'
xhr.onload = () => console.log(xhr.response)
// 监听xhr的请求取消事件
// 和xhr的timeout一样 xhr的abort方法 也是在浏览器层面 取消对某一个请求的处理
// 在网络中对应的请求依旧在传输,也可能会返回对应的结果
// 只不过浏览器不会在对该请求进行任何的处理
xhr.onabort = () => console.log('请求被主动取消了')
xhr.open('get', 'http://123.207.32.32:1888/01_basic/timeout')
// 过3s后,主动取消xhr的当前请求
setTimeout(() => xhr.abort(), 3000)
xhr.send()
ajax的简单封装
// XHR方法的简单封装
// 参数比较多,所以封装为一个配置对象
function request({
url,
method = 'get',
header = {},
timeout = 10000,
isRequestJson = true,
data = {}
} = {}) {
if (!url) {
throw new Error('url is required')
}
const xhr = new XMLHttpRequest()
const promise = new Promise((resolve, reject) => {
try {
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject({
code: xhr.status,
codeText: xhr.statusText
})
}
})
xhr.addEventListener('error', reject)
// 设置返回值格式
xhr.responseType = 'json'
// 设置超时时间
xhr.timeout = timeout
// 设置响应头
// setRequestHeader方法必须在open方法后才可以进行调用
function setRequestHeader() {
if(Object.keys(header).length) {
for (const key of Object.keys(header)) {
xhr.setRequestHeader(key, header[key])
}
}
}
if (method.toUpperCase() === 'GET') {
// 如果请求是get请求 同时设置了data参数
// 将其以urlencoded的方式 将对应的参数添加到url链接的最后
const requestUrl = new URL(url)
const searchObj = Object.fromEntries(new URLSearchParams(requestUrl.search).entries())
// 将url字符串中的query参数和data中传入的query参数进行合并
// 将URLSearchParams实例赋值给URL实例的search时,会自动调用其对应的toString方法
requestUrl.search = new URLSearchParams(Object.assign(searchObj, data))
// xhr.open的第二个参数即可以是字符串格式的url,也可以是URL的实例对象
// 如果传入的是URL对应的实例对象,会自动调用该实例对应的toString方法
xhr.open(method, requestUrl)
setRequestHeader()
xhr.send()
} else {
xhr.open(method, url)
setRequestHeader()
if (data instanceof FormData) {
xhr.send(data)
} else {
xhr.setRequestHeader('Content-Type', isRequestJson ? 'application/json' : 'application/x-www-form-urlencoded')
if (isRequestJson) {
xhr.send(JSON.stringify(data))
} else {
xhr.send(new URLSearchParams(data).toString())
}
}
}
} catch (e) {
reject(e)
}
})
// 将创建出来的xhr对象挂载到返回的promise对象上
// 以便于调用者可以拿到原生的XHR对象,并进行对应操作
// 如执行abort方法
promise.xhr = xhr
return promise
}
示例 文件上传
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XHR实现文件上传</title>
</head>
<body>
<input type="file" id="file">
<button id="btn">submit</button>
<script>
const fileEl = document.getElementById('file')
const btnEl = document.getElementById('btn')
btnEl.addEventListener('click', () => {
const xhr = new XMLHttpRequest()
xhr.open('post', 'http://www.example.com/upload')
xhr.onload = () => console.log(xhr.response)
// 监听上传进度
xhr.onprogress = e => console.log(e.loaded / e.total)
xhr.responseType = 'json'
// 当一个input的属性为type的时候,其对应的dom元素就会多一个files属性
// 其是一个数组,记录这所有上传的文件信息,每一个文件信息都是File对象的实例对象
const files = fileEl.files
// 目前前端文件上传都是依靠表单来进行完成和实现的
// 也就是说将文件对象取出,作为表单对象的一个key-value后
// 上传整个表单对象
const formData = new FormData()
formData.append('avatar', files[0])
xhr.send(formData)
})
</script>
</body>
</html>