Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML”
手写 Ajax
建议先阅读MDN
创建异步对象
let xmlHttp
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlHttp = new XMLHttpRequest()
} else {
// code for IE6, IE5
xmlHttp = new ActiveXObject('Microsoft.XMLHTTP')
}
创建的这个异步对象上有很多属性和方法
-
onreadystatechange:监听异步对象请求状态码readyState的改变,每当readyState改变时,就会触发onreadystatechange事件 -
readyState:表示异步对象目前的状态,状态码从0到40: 表示请求未初始化,还没有调用
open();1: 服务器连接已建立,但是还没有调用
send();2: 请求已接收,正在处理中(通常现在可以从响应中获取内容头);
3: 请求处理中,通常响应中已有部分数据可用了,没有全部完成;
4: 当
readyState状态码为4时,表示请求已完成;此阶段确认全部数据都已经解析完毕,可以通过异步对象的属性获取对应数据; -
status:http状态码
表示成功的http状态码有
xmlHttp.status >= 200 && xmlHttp.status < 300 || xmlHttp.status == 304 -
responseText:后台返回的字符串形式的响应数据;
-
responseXML:后台返回的XML形式的响应数据
设置请求方式和请求地址
-
open()方法设置ajax请求方式和请求地址
-
xmlHttp.open("GET/POST","ajax-get.txt",true) -
请求的类型;GET 还是 POST;
-
表示请求的文件的地址url;
-
设置请求方法是不是异步async,true为异步, false为同步。AJAX存在的意义就是发异步请求,所以第三个参数永远传true
-
-
IE中的缓存问题
-
在IE浏览器中,如果通过Ajax发送GET请求,那么IE浏览器认为,同一个URL只有一个结果,如果地址没有发生变化,它就会把上一次返回的结果,直接返回
-
想拿到实时数据,必须保证每次的URL都是不一样的,有两种方式
-
Math.random()
-
new Date().getTime()
xmlHttp.open('GET', 'ajax-get.txt?t=' + new Date().getTime(), true) //或 xmlHttp.open('GET', 'ajax-get.txt?t=' + Math.random(), true) -
发送请求
xmlHttp.send()
- 发送POST请求,使用
setRequestHeader()来添加 HTTP请求头,并在send()方法中传递要发送的数据
xmlHttp.open('POST', 'ajax_test.html', true)
xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xmlHttp.send('fname=Henry&lname=Ford')
通过onreadystatechange监听状态变化
-
当异步对象的readyState发生改变,会触发
onreadystatechange函数 -
当readyState变成为4时,表示当前状态是请求完毕的状态
-
同时当http的响应码status为200到300之间(包括200和300)或为304时,表示ajax请求成功
-
当http状态码不是200到300之间的数也不是304时,表示请求不成功
//4.监听状态变化
xmlHttp.onreadystatechange = () => {
// 判断当前状态改变是请求完毕的状态吗
if (xmlHttp.readyState === 4) {
if ((xmlHttp.status >= 200 && xmlHttp.status < 300) || xmlHttp.status == 304) {
console.log('成功的接收到服务器返回的数据')
} else {
console.log('不成功!')
}
}
}
处理返回的结果
-
如果成功,可通过异步对象的
responseText属性来获取服务器返回的字符串 -
封装一个方法ajax()用于发送请求
-
URL当中只能出现字母 数字 下划线和ASCII码,不能出现中文,可以使用encodeURIComponent()转码
-
设置超时时间
-
ajax({
type: 'GET',
url: 'http://localhost:3000/posts',
timeout: 1000,
success: (data) => {
console.log('success', data)
},
error: (err) => {
console.log('error', err)
},
})
其他
jQuery官方的ajax还是有一定的差异
-
传递多个参数,需要保持传递顺序
-
改写成传递的是一个对象
-
传递的是一个对象就不用考虑先后顺序,里面用的参数通过对象名.属性名的形式获取
-
-
传递请求类型的区分大小写,jQuery官方的是大小写都可以
- 解决方案是可以使用
toLowerCase()或者toUpperCase()将类型转成大写或小写再对比
- 解决方案是可以使用
-
我们传递的数据用的名字是obj,jQuery官方用的是data
const ajax = (option) => {
//type, url, data, timeout, success, error将所有参数换成一个对象{}
// 0.将对象转换成字符串
//处理obj
const objToString = (data) => {
data.t = new Date().getTime()
let res = []
for (let key in data) {
//需要将key和value转成非中文的形式,因为url不能有中文。使用encodeURIComponent();
res.push(encodeURIComponent(key) + ' = ' + encodeURIComponent(data[key]))
}
return res.join('&')
}
let str = objToString(option.data || {})
// 1.创建一个异步对象xmlHttp;
var xmlHttp, timer
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest()
} else if (xmlHttp) {
// code for IE6, IE5
xmlHttp = new ActiveXObject('Microsoft.xmlHttp')
}
// 2.设置请求方式和请求地址;
// 判断请求的类型是POST还是GET
if (option.type.toLowerCase() === 'get') {
xmlHttp.open(option.type, option.url + '?t=' + str, true)
// 3.发送请求;
xmlHttp.send()
} else {
xmlHttp.open(option.type, option.url, true)
// 注意:在post请求中,必须在open和send之间添加HTTP请求头:setRequestHeader(header,value);
xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
// 3.发送请求;
xmlHttp.send(str)
}
// 4.监听状态的变化;
xmlHttp.onreadystatechange = function () {
clearInterval(timer)
debugger
if (xmlHttp.readyState === 4) {
if ((xmlHttp.status >= 200 && xmlHttp.status < 300) || xmlHttp.status == 304) {
// 5.处理返回的结果;
option.success(xmlHttp.responseText) //成功后回调;
} else {
option.error(xmlHttp.responseText) //失败后回调;
}
}
}
//判断外界是否传入了超时时间
if (option.timeout) {
timer = setInterval(function () {
xmlHttp.abort() //中断请求
clearInterval(timer)
}, option.timeout)
}
}
测试
- mock数据
{
"posts": [
{
"id": 1,
"title": "json-server",
"author": "xianzao"
}
],
"comments": [
{
"id": 1,
"body": "some comment",
"postId": 1
}
],
"profile": {
"name": "xianzao"
}
}
- 执行
ajax({
type: 'GET',
url: 'http://localhost:3000/posts',
timeout: 1000,
success: (data) => {
console.log('success', data)
},
error: (err) => {
console.log('error', err)
},
})
针对fusion中实现Upload的request封装
function customRequest(option) {
/* coding here */
return {abort() {/* coding here */}};
}
<Upload request={customRequest}/>
customRequest的实现
/**
* clone from https://github.com/react-component/upload/blob/master/src/request.js
*/
function getError(option, xhr, msg) {
msg = msg || `cannot post ${option.action} ${xhr.status}'`
const err = new Error(msg)
err.status = xhr.status
err.method = option.method
err.url = option.action
return err
}
function getBody(xhr) {
const text = xhr.responseText || xhr.response
if (!text) {
return text
}
try {
return JSON.parse(text)
} catch (e) {
return text
}
}
// option {
// onProgress: (event: { percent: number }): void,
// onError: (event: Error, body?: Object): void,
// onSuccess: (body: Object): void,
// data: Object,
// filename: String,
// file: File,
// withCredentials: Boolean,
// action: String,
// headers: Object,
// method: String
// timeout: Number
// }
export default function upload(option) {
const xhr = new XMLHttpRequest()
if (option.onProgress && xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = (e.loaded / e.total) * 100
}
option.onProgress(e)
}
}
const formData = new FormData()
if (option.data) {
Object.keys(option.data).forEach((key) => {
formData.append(key, option.data[key])
})
}
if (option.file instanceof Blob) {
formData.append(option.filename, option.file, option.file.name)
} else {
formData.append(option.filename, option.file)
}
xhr.onerror = function error(e) {
option.onError(e)
}
xhr.onload = function onload() {
// allow success when 2xx status
// see https://github.com/react-component/upload/issues/34
if (xhr.status < 200 || xhr.status >= 300) {
return option.onError(getError(option, xhr), getBody(xhr))
}
option.onSuccess(getBody(xhr), xhr)
}
option.method = option.method || 'POST'
xhr.open(option.method, option.action, true)
// In Internet Explorer, the timeout property may be set only after calling the open() method and before calling the send() method.
// see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout
const { timeout } = option
if (typeof timeout === 'number' && timeout > 0) {
xhr.timeout = timeout
xhr.ontimeout = () => {
const msg = `Upload abort for exceeding time (timeout: ${timeout}ms)`
option.onError(getError(option, xhr, msg), getBody(xhr))
}
}
// Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
if (option.withCredentials && 'withCredentials' in xhr) {
xhr.withCredentials = true
}
const headers = option.headers || {}
// when set headers['X-Requested-With'] = null , can close default XHR header
// see https://github.com/react-component/upload/issues/33
if (headers['X-Requested-With'] !== null) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
}
for (const h in headers) {
if (headers.hasOwnProperty(h) && headers[h] !== null) {
xhr.setRequestHeader(h, headers[h])
}
}
xhr.send(formData)
return {
abort() {
xhr.abort()
},
}
}