Fetch添加超时和拦截器功能
2019-07-29 本文已影响0人
ZZES_ZCDC
Fetch介绍
Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()
方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。这种功能以前是使用 XMLHttpRequest
实现的。Fetch提供了一个更好的替代方法,可以很容易地被其他技术使用,例如 Service Workers
。Fetch还提供了单个逻辑位置来定义其他HTTP相关概念,例如CORS和HTTP的扩展
超时和拦截器
超时是XMLHttpRequset自带的功能, 但是Fetch却没有...
拦截器是axios里的特色功能, 可以对请求前的动作和接受响应后的动作进行拦截, 处理.
超时实现
核心就是使用Promise.race()方法, 将Fetch和用Promise包裹的定时器放在数组里传入, 先触发resolve的将触发Promise.race()的resolve
所以当定时器的Promise先完成, 就会直接跳出, 抛出超时错误
示例代码:
if (env === 'browser' && !window.fetch) {
try {
require('whatwg-fetch')
this.originFetch = window.fetch;
} catch (err) {
throw Error('No fetch avaibale. Unable to register fetch-intercept');
}
} else if(env === 'browser' && window.fetch) {
// fixed: Fetch TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
this.originFetch = window.fetch.bind(window)
}
if (env === 'node') {
try {
this.originFetch = require('node-fetch');
} catch (err) {
throw Error('No fetch avaibale. Unable to register fetch-intercept');
}
}
this.newFetch = (url: string, opts: object): Promise<any> => {
const fetchPromise: Promise<any> = this.originFetch(url, opts);
const timeoutPromise: Promise<any> = new Promise(function (resolve, reject) {
setTimeout(() => {
reject(new Error(`Fetch Timeout ${timeout}`));
}, timeout);
})
return Promise.race([fetchPromise, timeoutPromise]);
}
拦截器实现
拦截器也是使用Promise实现, 将请求相关拦截器, Fetch请求, 响应相关拦截器堆叠起来, 实现拦截处理. 正如示例代码, 通过Promise进行同步调用
示例代码:
private packageIntercept(fetch: Function, ...args: any[]): Promise<any> {
let promise = Promise.resolve(args);
this.interceptArr.forEach(({ request, requestError }: InterceptFuncObj) => {
if (request && requestError) {
promise = promise.then((args: any[]) => request(...args), requestError());
} else if (request && !requestError) {
promise = promise.then((args: any[]) => request(...args));
} else if (!request && requestError) {
promise = promise.then((args: any[]) => args, requestError());
}
})
promise = promise.then((args: any[]) => fetch(...args));
this.interceptArr.forEach(({ response, responseError }: InterceptFuncObj) => {
if (response && responseError) {
promise = promise.then((args: any[]) => response(...args), (args: any[]) => responseError(...args));
} else if (response && !responseError) {
promise = promise.then((args: any[]) => {
return response(args)
});
} else if (!response && responseError) {
promise = promise.then((args: any[]) => args, (e: any) => responseError(e));
}
})
return promise;
}
}
完整代码
import "core-js/fn/promise"
export default class zFetchz {
private interceptArr: InterceptFuncObj[] = [];
public originFetch: Function = () => {};
public newFetch: Function = () => {};
public interceptor: InterceptInitObj = {
register: () => {},
clear: () => {}
};
public constructor(env: string, timeout: number = 5000) {
if (env === 'browser' && !window.fetch) {
try {
require('whatwg-fetch')
this.originFetch = window.fetch;
} catch (err) {
throw Error('No fetch avaibale. Unable to register fetch-intercept');
}
} else if(env === 'browser' && window.fetch) {
// fixed: Fetch TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
this.originFetch = window.fetch.bind(window)
}
if (env === 'node') {
try {
this.originFetch = require('node-fetch');
} catch (err) {
throw Error('No fetch avaibale. Unable to register fetch-intercept');
}
}
this.newFetch = (url: string, opts: object): Promise<any> => {
const fetchPromise: Promise<any> = this.originFetch(url, opts);
const timeoutPromise: Promise<any> = new Promise(function (resolve, reject) {
setTimeout(() => {
reject(new Error(`Fetch Timeout ${timeout}`));
}, timeout);
})
return Promise.race([fetchPromise, timeoutPromise]);
}
this.interceptor = this.init();
}
/**
* init interceptor
*/
private init(): InterceptInitObj {
const that = this;
this.newFetch = (function (fetch: Function): Function {
return function (...args: any[]): Promise<any> {
return that.packageIntercept(fetch, ...args);
}
})(this.newFetch)
return {
register: (interceptFuncObj: InterceptFuncObj): Function => {
this.interceptArr.push(interceptFuncObj);
return () => { // use to unregister
const index: number = this.interceptArr.indexOf(interceptFuncObj);
if (index >= 0) {
this.interceptArr.splice(index, 1);
}
}
},
clear: (): void => {
this.interceptArr = [];
}
}
}
/**
* setting the request and response intercept
*/
private packageIntercept(fetch: Function, ...args: any[]): Promise<any> {
let promise = Promise.resolve(args);
this.interceptArr.forEach(({ request, requestError }: InterceptFuncObj) => {
if (request && requestError) {
promise = promise.then((args: any[]) => request(...args), requestError());
} else if (request && !requestError) {
promise = promise.then((args: any[]) => request(...args));
} else if (!request && requestError) {
promise = promise.then((args: any[]) => args, requestError());
}
})
promise = promise.then((args: any[]) => fetch(...args));
this.interceptArr.forEach(({ response, responseError }: InterceptFuncObj) => {
if (response && responseError) {
promise = promise.then((args: any[]) => response(...args), (args: any[]) => responseError(...args));
} else if (response && !responseError) {
promise = promise.then((args: any[]) => {
return response(args)
});
} else if (!response && responseError) {
promise = promise.then((args: any[]) => args, (e: any) => responseError(e));
}
})
return promise;
}
}
interface InterceptInitObj {
register: Function,
clear: Function
};
interface InterceptFuncObj {
request?: Function,
requestError?: Function,
response?: Function,
responseError?: Function
};
使用方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../dist/zfetchz.umd.js"></script>
<script>
var z = new zfetchz('browser')
var zfetchzInterceptor = z.interceptor
zfetchzInterceptor.register({
request: function (...request) {
console.log('request', request)
return request
},
response: function (response) {
console.log('response', response)
return response
}
})
z.newFetch('https://cnodejs.org/api/v1/topics', {
headers: new Headers({
'content-type': 'application/json'
})
}).then(res => res.json())
.then(res => {
console.log(res)
})
</script>
</body>
</html>