手写ajax
GET请求
let xmlhttp;
if (window.XMLHttpRequest)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
console.log(xmlhttp.response); // 设置responseType为json时,用response获取数据,无需JSON.parse
}
if (xmlhttp.readyState === 4 && xmlhttp.status !== 200) {
throw new Error(`请求出错,错误码:${xmlhttp.status},错误说明: ${xmlhttp.statusText}`);
}
}
xmlhttp.open("GET", url, true);
// 服务器返回的数据为json数据,不需要parse转换
// 不设置的话客户端默认收到的是字符串,客户端需要parse转换成json数据
xmlhttp.responseType = "json";
xmlhttp.send();
POST请求
let xmlhttp;
if (window.XMLHttpRequest)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
console.log(xmlhttp.response);
}
if (xmlhttp.readyState === 4 && xmlhttp.status !== 200) {
throw new Error(`请求出错,错误码:${xmlhttp.status},错误说明: ${xmlhttp.statusText}`);
}
}
xmlhttp.open("POST", url, true);
// 发送给服务器的为json数据,即请求头的content-type类型为json类型
// 不设置的话服务器默认收到的是字符串,服务器需要parse转换成json数据类型
xmlhttp.setRequestHeader("Content-Type", "application/json");
// 服务器返回的数据为json数据,不需要parse转换
// 不设置的话客户端默认收到的是字符串,客户端需要parse转换成json数据
xmlhttp.responseType = "json";
xmlhttp.send(JSON.stringify({method: "ajax",sendType: "post"}));
发送json数据
方法一
设置xmlhttp.responseType
,发送时用JSON.stringify
将对象转化为字符串,添加请求头content-type
类型为application/json
,接收数据用xmlhttp.response
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
console.log(xmlhttp.response);
}
}
xmlhttp.open("POST", url, true);
// 发送给服务器的为json数据,即请求头的content-type类型为json类型
// 不设置的话服务器默认收到的是字符串,服务器需要parse转换成json数据类型
xmlhttp.setRequestHeader("Content-Type", "application/json");
// 服务器返回的数据为json数据,不需要parse转换
// 不设置的话客户端默认收到的是字符串,客户端需要parse转换成json数据
xmlhttp.responseType = "json";
xmlhttp.send(JSON.stringify({method: "ajax",sendType: "post"}));
方法二
发送时JSON.stringify格式化发送数据为json,服务器接收数据后需要调用JSON.parse将字符串转化为对象,服务器发送数据给客户端时,需要调用JSON.stringify。客户端接收数据时需要调用JSON.parse
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
console.log(JSON.parse(xmlhttp.responseText));
}
}
xmlhttp.open("POST", url, true);
xmlhttp.send(JSON.stringify({method: "ajax",sendType: "post"}));
设置超时
// 默认为0,即为不超时
xmlhttp.timeout = 4000;
跨域携带cookie
xhr.withCredentials = true;
终止请求
xmlhttp.abort();
设置请求头
xmlhttp.setRequestHeader("Content-Type", "application/json");
设置响应类型
// ""(默认值等同于text)、arraybuffer、blob、document、json、text
// 其中如果设置json及document,但是服务器返回的据不是指定类型的化,response的值为null
xmlhttp.responseType = "json";
强制设置响应类型
xmlhttp.overrideMimeType('text/xml');
获取响应头
xmlhttp.getResponseHeader("Content-Type")
axios的用法
GET请求
axios.get(url).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
POST请求
axios.post(url, {
"firstName": 'Fred',
"lastName": 'Flintstone'
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
添加请求头
// `headers` are custom headers to be sent
headers: {'X-Requested-With': 'XMLHttpRequest'},
设置超时时间
// 请求超时时间(毫秒)
timeout: 1000,
跨域携带cookie
// 是否携带cookie信息
withCredentials: false, // default
设置响应类型
// 响应格式
// 可选项 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // 默认值是json
终止请求
var CancelToken = axios.CancelToken;var source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}});
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
var CancelToken = axios.CancelToken;var cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})});
// cancel the request
cancel();
相关链接:
axios中文文档
fetch的用法
Fetch底层并不是XMLHttp技术(ajax),Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。得益于 JavaScript 实现的这些抽象好的 HTTP 模块,其他接口能够很方便的使用这些功能。除此之外,Fetch 还利用到了请求的异步特性——它是基于 Promise 的。
GET请求
fetch(url)
.then(function (response) {
// debugger;
// let data = response.text(); // json 数据转字符串,不报错
// let data = response.formData(); // json 报错 类型错误
// let data = response.json(); // json 不报错,json
// let data = response.arrayBuffer(); // json 不报错, arraybuffer
// let data = response.blob(); // json 不报错, blob
// let data = response.text(); // string 不报错 string
// let data = response.formData(); // string 报错 类型错误
// let data = response.json(); // string 报错 类型错误
// let data = response.arrayBuffer(); // string 不报错, arraybuffer
// let data = response.blob(); // string 不报错, blob
return data;
})
.then(function (myJson) {
// debugger;
console.log(myJson);
});
注意:
- 第一个then方法为已经发送成功并收到服务器返回的数据,准备处理response响应体数据,第二个then方法为处理完成response响应体数据。
- 响应体数据处理时,如果服务器返回数据与准备获取的类型(
let data = response.text()
这里的text即为客户端准备获取的类型)不一致时,json和formData这两个会在不一致时报错,其余的不会报错。与ajax保持类似。
POST 请求
let data = {
method: "POST",
sendData: "adsf"
}
fetch(url, {
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json' // 不设置服务器不识别为json
},
method: "POST"
}).then(response => {
// let data = response.text(); // json 数据转字符串,不报错
// let data = response.formData(); // json 报错 类型错误
let data = response.json(); // json 不报错,json
// let data = response.arrayBuffer(); // json 不报错, arraybuffer
// let data = response.blob(); // json 不报错, blob
// let data = response.text(); // string 不报错 string
// let data = response.formData(); // string 报错 类型错误
// let data = response.json(); // string 报错 类型错误
// let data = response.arrayBuffer(); // string 不报错, arraybuffer
// let data = response.blob(); // string 不报错, blob
return data;
}).then(response => {
console.log(response);
})
注意:
- 如果不设置正确的header中的content-type类型的化,服务获取到的数据类型并不会是指定类型,这一点与ajax保持一致
添加请求头
fetch(url, {
headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
}
})
.then(response => response.json()) // parses response to JSON
获取响应头
fetch(url, {
headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
}
})
.then(response => {
console.log(response.headers);// 可通过这里的content-type类型决定返回哪种数据类型
if(response.headers.get("content-type") === "application/json") {
return response.json()
}
return response.text()
})
跨域携带cookie
// 注意fetch在不跨域的情况下是携带cookie的,接收set-cookie头。 在跨域的情况下是不携带cookie,不接受set-cookie
// 这一点和ajax一致
fetch(url, {credentials: 'include' })
超时设置
方案一 设置setTimeout
function request(url, wait) {
return new Promise((resolve, reject) => {
let status = 0; // 0 等待 1 完成 2 超时
// 设置定时器,超时reject,这个只有一个promise
let timer = setTimeout(() => {
if (status === 0) {
status = 2;
timer = null;
console.log("超时");
reject(new Error("request timeout error"));
}
}, wait);
fetch(url)
.then(res => res.json())
.then(res => {
// 清除定时器
if (status !== 2) {
clearTimeout(timer);
timer = null;
status = 1;
resolve(res);
}
});
});
}
// 调用
request(url).then(json => {
console.log(json);
}).catch(err => {
console.log(err);
// 处理超时
})
方案二 使用Promise.race
// 创建一个setTimeout的promise和一个fetch的promise,两个promise进行赛跑(race),如果setTimeout先完成,则结束promise.race。
function request(fetchPromise, timeout) {
return Promise.race([
fetchPromise,
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("超时了");
reject(new Error('request timeout')) // 创建Error
}, 3000)
})
]);
}
// 调用
request(fetch(url), 3000).then(response => response.json()).then(json => {
console.log(json);
}).catch(err => {
console.log(err) // 这个就是刚才创建的超时异常,或其他请求异常
// 这里处理超时
})
取消请求
方案一 通过reject取消Promise,并不会取消请求的发送
原理为劫持Promise,添加一个abort方法
// 劫持全局Promise
Promise.prototype.abort = () => Promise.reject(new Error("abort promise"));
// 用函数包装
function fetchAbort1(fetchPromise) {
fetchPromise.abort = () => Promise.reject(new Error("abort promise"));
return fetchPromise
}
// 示例
var p = fetch(url).then(response => response.json()).then(json => {
console.log(json);
}).catch(error => {
console.log(error);
});
setTimeout(() => {
console.log("4s后取消了")
p.abort()
}, 4000)
另一种实现(加上Promise.race)
function fetchAbort(fetchPromise) {
var abort_fn = null;
//这是一个可以被reject的promise
var abort_promise = new Promise(function (resolve, reject) {
abort_fn = function () {
reject('abort promise');
};
});
//这里使用Promise.race,以最快 resolve 或 reject 的结果来传入后续绑定的回调
var abortable_promise = Promise.race([
fetchPromise,
abort_promise
]);
abortable_promise.abort = abort_fn;
return abortable_promise;
}
// 调用
var p = fetchAbort(fetch('//a.com/b/c'));
p.then(function(res) {
console.log(res)
}, function(err) {
console.log(err);
});
//假设fetch要3秒,但是你想在2秒就放弃了:
setTimeout(function() {
p.abort(); // -> will print "abort promise"
}, 2000);
方案二 通过AbortController 取消fetch请求(处于实验阶段,慎用),会取消请求的发送,终止fetch请求
let controller = new AbortController();
let signal = controller.signal;
let timeoutPromise = (timeout) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new Response("timeout", { status: 504, statusText: "timeout " }));
controller.abort();
}, timeout);
});
}
let requestPromise = (url) => {
return fetch(url, {
signal: signal
});
};
Promise.race([timeoutPromise(1000), requestPromise("https://www.baidu.com")])
.then(resp => {
console.log(resp);
})
.catch(error => {
console.log(error);
});
参考链接:
Fetch超时设置和终止请求
AbortController
postMessage
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递,只适用于有iframe标签的页面
父页面
<iframe src="./child1.html" frameborder="1"></iframe>
<iframe src="./child2.html" frameborder="1"></iframe>
<script>
window.onload = function(params) {
var child1 = window.frames[0];
child1.postMessage('message from parentwindow', '*');// 给1发消息
var child2 = window.frames[1];
child2.postMessage('message from parentwindow', '*');// 给1发消息
window.addEventListener('message', function (e) {
console.log(e.data) // 接收消息
}, false)
}
</script>
子页面一
<h1>child1 page</h1>
<script>
window.onload = function() {
window.top.postMessage('message from child1', "*"); // 给父页面发消息
window.addEventListener('message', function (e) {
console.log("child1", e.data); // 接收数据
}, false)
}
</script>
子页面二
<h1>child2 page</h1>
<script>
window.onload = function() {
window.top.postMessage('message from child2', "*"); // 给父页面发消息
window.addEventListener('message', function (e) {
console.log("child2", e.data); // 接收数据
}, false)
}
</script>
参考连接:
window.postMessage
websocket
// 需要自定义头协议
// 需要保持心跳连接
// 需要断开重连
const sleep = time => new Promise(resolve => {
setTimeout(resolve, time);
})
let count = 0; // 链接次数
function soket(url, wsType = "arraybuffer") {
var ws = new WebSocket(url);
ws.binaryType = wsType; // 设置数据接收类型
ws.onopen = () => {
ws.send("Hello world!"); // 建立连接后发送验证消息
};
ws.onmessage = evt => {
console.log(evt.data); // 接收消息后的处理
ws.send("abcd");
}
ws.onclose = async() => {
console.log("ws 连接失败") // 断开连接后的处理
count++;
if (count < 5) {
await sleep(500);
soket(url, wsType);
}
};
return ws;
}
Server Sent Events
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。
总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。
SSE和websoket的区别
- SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
- SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
- SSE 默认支持断线重连,WebSocket 需要自己实现。
- SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
- SSE 支持自定义发送的消息类型。
基本用法
// 客户端
// 判断是否支持
if("EventSource" in window) {
// 生成EventSource实例
var source = new EventSource(url);
// 建立连接时触发
source.onopen = function (event) {
// ...
};
// 接收消息时触发
source.onmessage = function (event) {
var data = event.data;
// handle message
};
// 发生通信错误时触发
source.onerror = function (event) {
// handle error event
};
}
// 服务器端
// 发送以下响应头
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
// 发送响应体数据示例,每一条数据以\n\n结尾,每行按\n分割
: this is a test stream\n\n
data: some text\n\n
data: another message\n
data: with two lines \n\n
服务器发送的响应体字段
data
// 数据内容用data字段表示。
data: begin message\n // \n为换行
data: continue message\n\n // \n\n为消息结尾
data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n
event
// event字段表示自定义的事件类型,默认是message事件。浏览器可以用addEventListener()监听该事件。
event: foo\n
data: a foo event\n\n
data: an unnamed event\n\n
event: bar\n
data: a bar event\n\n
id
// id为每一条数据的标识,可在连接断线后,重新连接时同步信息使用, 因此,这个头信息可以被视为一种同步机制。客户端可以用lastEventId属性读取这个值
id: msg1\n
retry
// 服务器端发送以下数据,客户端会在报错后等待指定时间重新连接
// 适用于服务器主动关闭连接、网络出错及超时、时间间隔到期等。
retry: 10000\n
关闭SSE连接
source.close();
跨域携带cookie
var source = new EventSource(url, { withCredentials: true });
自定义事件
// 客户端 自定义事件foo
source.addEventListener('foo', function (event) {
var data = event.data;
// handle message
}, false);
// 服务器端 发送自定义事件foo
event: foo\n
data: a foo event\n\n
关闭、超时及网络出错时的重连
retry: 10000\n
示例
客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>测试SSE</title>
</head>
<body>
<div id="example"></div>
<script>
var source = new EventSource('http://127.0.0.1:8844/stream');
var div = document.getElementById('example');
source.onopen = function (event) {
div.innerHTML += '<p>Connection open ...</p>';
};
source.onerror = function (event) {
div.innerHTML += '<p>Connection close.</p>';
};
source.addEventListener('connecttime', function (event) {
div.innerHTML += ('<p>Start time: ' + event.data + '</p>');
}, false);
source.onmessage = function (event) {
div.innerHTML += ('<p>Ping: ' + event.data + '</p>');
};
</script>
</body>
</html>
服务器端
var http = require("http");
var count = 0;
http.createServer(function (req, res) {
var fileName = "." + req.url;
count++;
if (fileName === "./stream") {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": '*',
});
res.write(`: This is a ${count} comment\n`)
res.write("retry: 3000\n");
res.write(`id: ${count}\n`)
res.write("event: connecttime\n");
res.write("data: " + (new Date()) + "\n\n");
// res.end(); // 服务器关闭,客户端在retry时间段后会重新向服务器发送连接
// 定时发送数据给客户端
interval = setInterval(function () {
res.write("data: " + (new Date()) + "\n\n");
}, 1000);
// 关闭时清除定时器
req.connection.addListener("close", function () {
clearInterval(interval);
}, false);
}
}).listen(8844, "127.0.0.1");
相关连接:
Server-Sent Events 教程
Server-sent events
ajax、axios、fetch的区别
- ajax:底层是ajax,可设置超时,可取消请求,没有promise封装,自己处理非200的错误
- axios:底层是ajax,可设置超时,可取消请求,通过promise封装,非200的错误进入catch
- fetch:底层是fetch API,不可设置超时,不可取消请求,通过promise封装,非200的错误并不会catch
ajax断点续传
什么是短链接、长连接、短轮询、长轮询
- 短链接:请求-响应,响应完成后即关闭连接的为短链接
- 长连接:建立完成连接后,不会主动关闭连接,长时间保持客户端与服务器端的通讯
- 短轮询:客户端向服务器发送请求,连接建立很短时间后,服务器关闭请求,随即客户端再次向服务器发送请求。 与长轮询相比连接持续时间短,建立次数多。
- 长轮询:客户端向服务器发送请求,连接建立很长时间后,服务器关闭请求,随即客户端再次向服务器发送请求。与短轮询相比连接持续时间长,建立次数少。
什么是跨域?什么是同源策略,什么是cors
什么是跨域
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。"协议+域名+端口"不同会产生跨域。
什么是同源策略
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 Js对象无法获得
- AJAX 请求不能发送
什么是cors
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
跨域的几种方式
- 通过jsonp跨域
- document.domain + iframe跨域
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
- 跨域资源共享(CORS)
- nginx代理跨域
- nodejs中间件代理跨域
- WebSocket协议跨域
参考链接
详解跨域(最全的解决方案)