什么是AJAX?
通过交互式网站和现代 Web 标准,AJAX正在逐渐被 JavaScript 框架中的函数和官方的 Fetch API 标准取代。
XMLHTTPRequest
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/service');
// request state change event
xhr.onreadystatechange = function () {
// request completed?
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// request successful - show response
console.log(xhr.responseText);
} else {
// request error
console.log('HTTP error', xhr.status, xhr.statusText);
}
};
// xhr.timeout = 3000; // 3 seconds
// xhr.ontimeout = () => console.log('timeout', xhr.responseURL);
// progress事件可以报告长时间运行的文件上传
// xhr.upload.onprogress = p => {
// console.log(Math.round((p.loaded / p.total) * 100) + '%');
// }
// start request
xhr.send();
全部readyState状态值都在 XMLHTTPRequest.readyState,如下也是:
- 0 UNSENT Client has been created. open() not called yet.
- 1 OPENED open() has been called.
- 2 HEADERS_RECEIVED send() has been called, and headers and status are available.(服务器已收到请求)
- 3 LOADING Downloading; responseText holds partial data.(服务器正在处理请求)
- 4 DONE The operation is complete.
Fetch API
语法
Promise<Response> fetch(url[, option]);
参数
url
:URL字符串或者一个Request对象option
可选,一个配置项对象method
: 请求使用的方法,如 GET、POST。headers
:请求头信息body
:请求的body信息mode
: 请求的模式,如 cors、 no-cors 或者 same-origin。credentials
: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie , 必须提供这个选项。因为默认是不携带cookie的。cache
: 请求的 cache 模式: default、 no-store、 reload 、 no-cache 、 force-cache 或者 only-if-cached。signal
:一个AbortSignal对象实例,允许您与fetch请求进行通信,并在需要时通过AbortController中止它。
使用示例
HTTP错误(例如404 Page Not Found 或 500 Internal Server Error)不会导致Fetch返回的Promise标记为reject,从而catch方法也不会被执行。想要精确的判断 fetch是否成功,需要包含 promise resolved 的情况,此时再判断 response.ok是不是为 true
fetch('http://domain/service', { method: 'GET' }) .then(response => { if (response.ok) { return response.json(); } throw new Error('Network response was not ok.'); }) .then(json => console.log(json)) .catch(error => console.error('error:', error));
fetch不支持设置超时,需要使用promise
function fetchTimeout(url, init, timeout = 3000) { return new Promise((resolve, reject) => { fetch(url, init) .then(resolve) .catch(reject); setTimeout(reject, timeout); }) }
终止fetch
const controller = new AbortController(); fetch( 'http://domain/service', { method: 'GET', signal: controller.signal }) .then(response => response.json()) .then(json => console.log(json)) .catch(error => console.error('Error:', error)); controller.abort();
注意:
xhr的回调是宏任务,fetch基于promise实现,其回调是微任务
常见HTTP请求头/响应头/状态码
request header
- :method: GET
- :path:
- :scheme: https
- accept: application/json, text/plain, /
- accept-encoding: gzip, deflate, br
- cache-control: no-cache
- cookie: deviceId=c12;
- origin:
- referer:
- user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
response header
- access-control-allow-credentials: true
- access-control-allow-origin:
- content-encoding: gzip
- content-type: application/json;charset=UTF-8
- date: Thu, 06 Aug 2020 08:15:05 GMT
- set-cookie: sess=QvrAQ0Cq+EcDQQPTer2X;
- status: 200
status
- 200 get 成功
- 201 post 成功
- 301 永久重定向
- 302 临时重定向
- 304 协商缓存 服务器文件未修改
- 400 客户端请求有语法错误,不能被服务器识别
- 403 服务器受到请求,但是拒绝提供服务,可能是跨域
- 404 请求的资源不存在
- 405 请求的method不允许
- 500 服务器发生不可预期的错误
封装一个跨浏览器兼容的请求函数
interface IOptions {
url: string;
type?: "GET" | "POST";
data: any;
timeout?: number;
}
function formatUrl(object) {
// a=xxx&b=xxxx; querystring
let dataArr = [];
for (let key in object) {
dataArr.push(`${key}=${encodeURIComponent(object[key])}`);
}
return dataArr.join("&");
}
export function ajax(
options: IOptions = {
type: "GET",
data: {},
timeout: 3000,
url: "",
}
) {
return new Promise((resolve, reject) => {
if (!options.url) {
return;
}
const queryString = formatUrl(options.data);
const onStateChange = () => {
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
clearTimeout(timer);
if (
(xhr.status >= 200 && xhr.status < 300) ||
xhr.status === 304
) {
resolve(xhr.responseText);
} else {
reject(xhr.status);
}
}
};
};
let timer;
let xhr;
if ((window as any).XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
if (options.type.toUpperCase() === "GET") {
xhr.open("GET", `${options.url}?${queryString}`);
onStateChange();
xhr.send();
} else if (options.type.toUpperCase() === "POST") {
xhr.open("POST", options.url);
xhr.setRequestHeader(
"ContentType",
"application/x-www-form-urlencoded"
);
onStateChange();
xhr.send(options.data);
}
if (options.timeout) {
timer = setTimeout(() => {
xhr.abort();
reject("timeout");
}, options.timeout);
}
});
}