创建 xhr 请求
我们在根目录下创建adapters目录,在该目录下创建xhr.js文件,文件内容如下面代码所示。
xhrAdapter函数里面的核心就是通过new XMLHttpRequest()构造出一个请求对象,通过调用open、send方法发送一个 http 请求,同时通过监听readystatechange事件来处理请求的响应,例如readyState不等于 4,表示请求还没完成。我们通过parseHeaders函数处理响应头,通过settle返回响应数据,我们接下来实现这两个函数, xhrAdapter函数更多的细节我在代码里一一添加了注释,这里就不重复了
var settle = require("../core/settle");
var parseHeaders = require("../helpers/parseHeaders");
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 请求数据
var requestData = config.data;
// 该方法暂时不实现
function done() {}
// 构造一个xhr对象
var request = new XMLHttpRequest();
// 目前先直接拿到url路径, 我们后面会对这个url进行处理
// 暂时不用理会这些处理细节
var fullPath = config.url;
// xhrReq.open(method, url, async)
// method: HTTP方法,比如GET、POST
// url: 请求的URL
// async: 表示是否异步执行操作,默认为true
// 之后同样会对这个url进行处理,现在暂时不用理会
request.open(config.method.toUpperCase(), fullPath, true);
function onloadend() {
if (!request) {
return;
}
// 对响应头进行处理
var responseHeaders =
"getAllResponseHeaders" in request
? parseHeaders(request.getAllResponseHeaders())
: null;
// 如果config参数没有responseType,则默认为text类型
var responseData =
!config.responseType || config.responseType === "text" || responseType === 'json'
? request.responseText
: request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request,
};
// 我们在settle返回响应数据
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// 清除请求
request = null;
}
// 监听 ready state
request.onreadystatechange = function handleLoad() {
// readyState不等于4,表示请求还没完成,所以不做任何处理
if (!request || request.readyState !== 4) {
return;
}
// 请求出错,我们没有得到回复,浏览器返回的 status 为0,这将由onerror处理。
// 有一个例外:就是请求使用file:协议,即使请求成功,也会将状态返回为0
// 因此,只有在不使用file:协议,并且status===0的情况下,我们直接返回
if (
request.status === 0 &&
!(request.responseURL && request.responseURL.indexOf("file:") === 0)
) {
return;
}
setTimeout(onloadend)
};
if (!requestData) {
requestData = null;
}
request.send(requestData);
});
};
我们在parseHeaders方法中处理响应头,我们实现该方法,先在根目录下创建helpers文件夹,在文件夹里创建parseHeaders.js文件
该方法的功能非常简单,就是把响应头的键值对保存到一个对象中去,因为响应头以换行符连接在一起,我们通过split('\n')方法就能拿到一个个响应头,由于单个响应头的格式是以:分割,如content-length: 1024,:前面部分为响应头的 key,后面部分为值
"use strict";
var utils = require("../utils");
// 忽略以下的请求头
var ignoreDuplicateOf = [
"age",
"authorization",
"content-length",
"content-type",
"etag",
"expires",
"from",
"host",
"if-modified-since",
"if-unmodified-since",
"last-modified",
"location",
"max-forwards",
"proxy-authorization",
"referer",
"retry-after",
"user-agent",
];
/**
* 解释一个请求头到对象中
*
* ```
* Date: Wed, 27 Aug 2014 08:58:49 GMT
* Content-Type: application/json
* Connection: keep-alive
* Transfer-Encoding: chunked
* ```
*
* @param {String} headers 要解释的请求头
* @returns {Object} 返回解释后的对象
*/
module.exports = function parseHeaders(headers) {
var parsed = {};
var key;
var val;
var i;
if (!headers) {
return parsed;
}
utils.forEach(headers.split("\n"), function parser(line) {
i = line.indexOf(":");
// 请求头的键
key = utils.trim(line.substr(0, i)).toLowerCase();
// 请求头的值
val = utils.trim(line.substr(i + 1));
if (key) {
if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {
return;
}
// cookie要合并起来
if (key === "set-cookie") {
parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);
} else {
parsed[key] = parsed[key] ? parsed[key] + ", " + val : val;
}
}
});
return parsed;
};
我们在根目录下创建utils.js文件,该文件里实现一些辅助函数,我们先实现forEach、trim辅助函数
"use strict";
// 判断值是不是一个数组
function isArray(val) {
return Array.isArray(val);
}
/**
* 处理字符串开始和结尾的空格
*
* @param {String} str 要处理的字符串
* @returns {String} 首尾没有多余空格的字符串
*/
function trim(str) {
// \uFEFF:字节序标记(Byte Order Mark)
// \xA0:禁止换行空白符
return str.trim ? str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
/**
* @param {Object|Array} obj 要迭代的对象
* @param {Function} fn 每个item要调用的回调函数
*/
function forEach(obj, fn) {
// 没有值直接返回
if (obj === null || typeof obj === "undefined") {
return;
}
// 不是对象类型强转为数组
if (typeof obj !== "object") {
/*eslint no-param-reassign:0*/
obj = [obj];
}
if (isArray(obj)) {
// 迭代数组的值
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// 如果值是一个对象类型,则对键值对进行迭代处理
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
module.exports = {
isArray,
forEach: forEach,
trim: trim,
};
最后我们要在core文件夹下创建settle.js文件,具体代码如下
module.exports = function settle(resolve, reject, response) {
// 我们将在第二章讲解config的各种默认属性,我们到时候再实现validateStatus方法,
// 这里暂不实现该方法,即暂时不对响应状态码进行校验
var validateStatus = response.config.validateStatus;
// 如果状态码存在,并且存在验证状态码方法,则直接用validateStatus方法验证状态码
// 如果状态码存在,验证状态码的方法不存在,则直接resolve
// 如果response.status = 0,表明file:协议请求成功,也直接resolve
// 验证不通过,则reject
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
// 错误处理这块,我们之后会专门讲到
// 现在暂时返回一个空{}
reject({});
}
};
我们在下一节创建一个axios实例