本文已参与「新人创作礼」活动,一起开启掘金创作之路。
- 手动实现一个简单的ajax, 通过xhr对象,去写个简单的流程。
function ajax(url,onSuccess,onFailed) {
const xhr = window.XMLHTTPRequest ? new XMLHTTPRequest() : new ActiveXObject();
xhr.open("get",url,true);
xhr.send();
xhr.onreadystatechange = function () {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
onSuccess && onSuccess(xhr.responseText);
} else {
onFailed && onFailed();
}
}
}
}
- 封装请求的拦截
添加一些请求的拦截,设置请求超时时间,处理请求成功时的响应,
请求的拦截一般干什么? 请求的拦截一般做一些公共校验,如果通过返回config,resolve后继续往下链
响应的拦截一般干什么? 拦截错误,公共的请求报错,处理返回数据等
// ajax.html
const adaptor = function (config) {
return new Promise((resolve, reject) => {
const xhr = window.XMLHttpRequest ? new XMLHttpRequest()
: new ActiveXObject("Mirosoft.XMLHttp");
xhr.open(config?.method.toUpperCase(), config?.url, true);
xhr.send();
xhr.timeout = config?.timeout;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject("request error");
}
}
};
});
};
// 处理请求成功时的响应
const dispatchRequest = function (config) {
return adaptor(config).then(
function (res) {
return res;
},
function (reason) {
return Promise.reject(reason);
}
);
};
const req = function (config) {
const chain = [dispatchRequest, undefined];
if (config?.interceptor) {
chain.unshift(
config?.interceptor?.fullfilled,
config?.interceptor?.rejected
);
}
if (config?.adaptor) {
chain.push(config?.adaptor?.fullfilled, config?.adaptor?.rejected);
}
let promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
req({
method: "get",
url: "http://127.0.0.1:5500/data.json",
interceptor: {
fullfilled: function (e) {
console.log("请求被拦截住了", e);
return e; // 一定要把config返回
},
},
adaptor: {
fullfilled: function (e) {
console.log("响应被拦截住了", e);
return e;
},
},
});
// data.json
{
"code": 0,
"data": "返回数据了",
"message": "成功",
"success": true
}
结果:
一定要把config返回,因为整体是链式结构的。resovePromise会把return的值resolve掉,作为接下来.then的result,否则拦截过后的页面的请求将获取不到数据和错误原因。
- 实现一种请求的取消 写一个on/emit去绑定事件,在处理请求的时候处理,从外部得到的config.cancel,在这里用on绑定了abort事件为,cancel传进来的函数,接收一个参数,作为cancel的参数,然后请求中emit执行abort,并将取消请求的方法,报错等等作为一个参数传入emit,而我们最终的目的就是能在外部,去调用这个参数,所以当emit的时候,把取消请求的方法传入绑定的事件中,又通过刚刚on那里穿到cancel,这个时候我们只要将cancel回调中的参数执行就可以取消请求。
我们还做了request的拦截和response的拦截,将请求的响应导入到一个数组chain,将request的拦截放到请求响应的前面,将response的拦截响应放到请求响应之后,这样[interceptorFullFilled,interceptorFullRejected,fullFilled,rejected,adAptorFullfilled,adAptorRejected] 然后我们从数组的前面往后两个一组执行,就可以让请求依次执行request的拦截,请求,response的拦截。
let cc = null;
const mitt = {
cache: {},
on: function (name, func) {
this.cache[name] = func;
},
emit: function (name, data) {
const fn = this.cache[name];
fn && fn(data);
},
};
const adaptor = function (config) {
return new Promise((resolve, reject) => {
let xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject("Mirosoft.XMLHttp");
xhr.open(config?.method.toUpperCase(), config?.url, true);
xhr.send();
xhr.onabort = function () {
xhr = null;
reject("[abort] request is aborted");
};
if (config.cancel) {
mitt.emit("abort", function () {
xhr.abort();
xhr = null;
reject("【abort】current request is abort.");
});
}
xhr.timeout = config?.timeout;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
setTimeout(function () {
resolve(xhr && xhr.responseText);
}, 5000);
} else {
reject("request error");
}
}
};
});
};
// 处理请求成功时的响应
const dispatchRequest = function (config) {
return adaptor(config).then(
function (res) {
return res;
},
function (reason) {
return Promise.reject(reason);
}
);
};
const req = function (config) {
const chain = [dispatchRequest, undefined];
if (config?.interceptor) {
chain.unshift(
config?.interceptor?.fullfilled,
config?.interceptor?.rejected
);
}
if (config?.adaptor) {
chain.push(config?.adaptor?.fullfilled, config?.adaptor?.rejected);
}
if (config.cancel) {
mitt.on("abort", function (func) {
config.cancel(func);
});
}
let promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
req({
method: "get",
url: "http://127.0.0.1:5500/3browser/01/data.json",
interceptor: {
fullfilled: function (e) {
console.log("请求被拦截住了", e);
return e; // 一定要把config返回
},
},
adaptor: {
fullfilled: function (e) {
console.log("响应被拦截住了", e);
return e;
},
},
cancel: function (onCancel) {
cc = onCancel;
},
});
setTimeout(function () {
cc && cc();
}, 2000);