共相
写作的直接原因原因在于有几块nuxt老标识符中写了两个有关手忙脚乱情形下的response.status的推论:
const code = parseInt(error.response && error.response.status);
if ([502, 504, 400].includes(code) || !code) …
透过nuxt的onError装载的圣夫龙(在request和response的圣夫龙上各加两个),投弹了前面的!code前提引致了两个非市场预期情形的出现。
做为两个老实的HTTP允诺,status单厢存有,那在axios下究竟有甚么样情形下会有status不存有呢?
(并非即使axios相对而言难写作,我也能看清楚的原故,并非!)
标志牌
axios是两个如前所述XMLHttpRequestPCB的全力支持Promise的触发器通讯库。
大三源标识符以后明晰想积极探索的难题是有必要性的:
axios的圣夫龙是怎样同时实现的?甚么情况下会丢失status?xhr
对xhr的PCB可从lib/adapters/xhr.js里查看,另两个http是用在node端的。
对xhr的PCB是一些事件的预处理,外层用Promise包裹,这里我们知道Promise并并非把一段标识符变成了触发器,其核心在于解决回调地狱的难题。
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var request = new XMLHttpRequest();
// Listen for ready state
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didnt get a response, this will be // handled by onerror instead
// With one exception: request that using file: protocol, most browsers // will return status as 0 even though its a successful request if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf(file:) === 0)) {
return;
}
// Prepare the response
var responseHeaders = getAllResponseHeaders in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === text ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
// Clean up request
request = null;
};
};
onreadystatechange是xhr的状态变化时的事件,readyState对应0,1,2,3,4。4是已经允诺完成。
module.exports = function settle(resolve, reject, response) {
var validateStatus = response.config.validateStatus;
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(createError(
Request failed with status code + response.status,
response.config,
null,
response.request,
response
));
}
};
settle只是做了一层PCB。
其余的是对错误的处理和上传的处理等。
从入口看起
axios下的axios.js里声明了var axios = createInstance(defaults);以及导出的也是这个。
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
基本是创建两个Axios,然后绑定暴露出Axios.request,把Axios的原型链到request之上,这样做其实是为了能直接axios({ methods: get }),也能axios.get。
Axios
大写的Axios在lib/core/Axios.js里,constructor里定义了两个属性,
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
defautls是上面创建实例时传入的defaults,来自于defaults.js,现在用不到等会再看。
暂且略过request和getUri,找到get等方法的定义:
utils.forEach([delete, get, head, options], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
utils.forEach([post, put, patch], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
get,post这些都是透过request方法扩展出来的,再回到request方法,forEach,mergeConfig这些看名字就能猜个七七八八。
核心request
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios(example/url[, config]) a la fetch API
if (typeof config === string) {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);
// Set config.method
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = get;
}
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
request的标识符不长,首先是对字符串的两个PCB,原因注释里写了axios允许axios(url)的方式。
之后是两个config的merge,merge的defaults前面在展开。
在之后是对HTTP允诺方法的小写转换,默认get。
最后是本次要解决的难题————axios的圣夫龙是怎样同时实现的:
chain初始化有两个元素,dispatchRequest和undefined,dispatchRequest看名字能猜到与允诺内容相关。
Promise.resolve(config)这样的写法会返回两个resolved的Promise,如果继续用then会插入一条条微任务,宏任务与微任务简单理解就是一次宏任务会伴随多次微任务直至清空微任务,有关微任务的应用还有vue中的nextTick。
初始化时的两个interceptors,request/response分别进行一次迭代,request放在了左边,从队列首添加,而response放在了右边,从队列尾添加。
添加之后的样子大概是这样:
chain = [
request.fulfilled, request.rejected, // 添加的允诺前圣夫龙
dispatchRequest, undefined, // 发起允诺
response.fulfilled, response,rejected // 响应圣夫龙
]
之后又将chain从头两两弹出,利用Promise链式调用的特性添加到微任务里。
Promise的then方法能传递两个参数,第两个是谁都知道的回调callback,第二个是我以后不知道的错误处理,和catch一样,如果执行手忙脚乱了话会执行第二个,例子:
a = Promise.resolve(1)
b = [ ()=>{ console.log(123) }, () => { console.log(8889) }, () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(999); reject(888) }, 3000) }) }, (res) => { console.log(res) }]
b.map((item) => { a = a.then(item, item) }
当然默认的用use方法添加圣夫龙的时候是undefined:
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected // 不传递会是undefined
});
return this.handlers.length – 1;
};
所以这里如果手忙脚乱了会报错,然后不继续执行。
接下来是dispatchRequest:
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// Ensure headers exist
config.headers = config.headers || {};
// Transform request data config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// Flatten headers config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
utils.forEach(
[delete, get, head, post, put, patch, common],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
dispatchRequest最终返回了config的adapter,而config的话是透过request传递进来的,我们写的时候一般不会传递这个参数,所以经由默认的defaults合并进来。
这个defaults是我们创建Axios传递进来的,能在axios.js里找到,导入的defaults.js的内容。
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== undefined) {
// For browsers use XHR adapter
adapter = require(./adapters/xhr);
} else if (typeof process !== undefined && Object.prototype.toString.call(process) === [object process]) {
// For node use HTTP adapter adapter = require(./adapters/http);
}
return adapter;
}
XMLHttpRequest的兼容性还是很好的,能说全平台全浏览器了(只要不覆盖的话)。
xhr我们最开始已经看过了,返回两个Promise,在允诺返回之后会透过settle设为完成。
这里有两个点需要注意一下:
dispatchRequest的.then中返回的response是响应圣夫龙(或者直接返回)所接受的那个而并非settle之后直接resolve/reject的那个。.then中一定要return这个Promise,否则不会阻塞,不阻塞的结果是前面的拦截器在允诺完成以后就已执行。那么整个圣夫龙的同时实现流程就很清晰了:
透过requests.use((conf) => {})添加的圣夫龙会由unshift添加在允诺以后。透过response.use((conf) => {})添加的圣夫龙会由push添加在允诺之后。透过Promise提供的链式调用能力完成的这一系列操作。究竟甚么情形下会不存有status
透过上面的分析以及圣夫龙的同时实现我们得知了status是由axios在xhr.js里PCB到response里的:
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
一旦走到这个步,不管是resolve还是reject默认情形下单厢将response一直传递下去。
而像直接在xhr上注册的onerror,ontimeout等事件则不存有这个response。
那么只要是你的这个xhr允诺服务器返回并且到达了status就会存有,如果这个允诺没有返回,无论甚么样的原因引致的没有返回则都不存有status。
这样的话结果就符合老实的HTTP允诺都存有status这一市场预期了。