业务场景
在发起网络请求时,往往需要对请求进行预处理或后处理。例如:
- 路由过滤器:在发起请求之前,可以使用中间件来检查请求的
URL,并根据特定条件重定向请求,例如将HTTP请求转换为HTTPS请求。 - 请求参数处理:用于修改请求参数,如添加自定义的请求头、验证请求参数的有效性或对请求体进行加密解密等操作。
- 错误处理:捕获请求过程中的错误,并进行适当的处理,例如记录日志、返回特定的错误响应或触发相应的错误处理逻辑。
- 缓存控制:实现请求结果的缓存控制,如检查缓存是否存在并有效,如果有效则直接返回缓存结果,否则再发送网络请求获取最新结果。
- 认证和授权:处理用户认证和授权逻辑,如检查用户的登录状态、验证访问权限或生成访问令牌等。
- 数据转换:对请求和响应的数据进行转换,如将请求的
JSON数据转换为实体对象或将响应的数据进行格式化处理。
假定我们已经有了一个fetch请求,例如:
fetch('http://xxx.com')
中间件1将HTTP请求转化为HTTPS
const mid1 = function (url: string, options: any, next: (...args: any[]) => any) {
if (url.startsWith('http')) {
url = url.replace('http', 'https')
}
return next(url, options)
}
中间件2加一个自定义的header
const mid2 = function (url: string, options: any, next: (...args: any[]) => any) {
const { header } = options || {}
options.header = { ...header, test1: 'test1' }
if (...) {
return next(url, options)
} else {
return
}
}
那么如何把fetch请求包装成wrappedFetch,使得这两者传入相同的参数,不同的是wrappedFetch的结果经过mid1和mid2的处理?
迭代的方式包装中间件
一个简单demo如下:
function middleWrapper(fetch, middleArr) {
return middleArr.reduceRight((next, middleware) => {
return (...args) => {
return middleware(next,...args);
};
}, fetch);
}
function fetch(url, data) {
console.log("Fetching data from", url);
console.log("Data:", data);
}
function middleware1(next,url,data) {
console.log("Middleware 1 called with args:", url, data);
next(url, data);
}
function middleware2(next,url,data) {
console.log("Middleware 2 called with args:", url, data);
next(url, data);
}
const middleArr = [middleware1, middleware2];
const wrappedFetch = middleWrapper(fetch, middleArr);
wrappedFetch("https://example.com/api", { key: "value" });
// 输出:
// Middleware 2 called with args: [ 'https://example.com/api', { key: 'value' }, [Function: fetch] ]
// Middleware 1 called with args: [ 'https://example.com/api', { key: 'value' }, [Function] ]
// Fetching data from https://example.com/api // Data: { key: 'value' }
通过闭包的方式包装中间件。 执行过程如下:
-
执行
wrappedFetch = middleWrapper(fetch, middleArr);next = fetchmiddleware = middleware2返回函数(...args) => middleware(next,...args)next = (...args) => middleware(next,...args)middleware = middleware1返回函数(...args) => middleware(next,...args)- 执行结果为
wrappedFetch = (...args) => middleware(next,...args)
-
调用
wrappedFetch("``https://example.com/api``", { key: "value" })"``https://example.com/api``", { key: "value" }作为参数,箭头函数返回结果middleware(next,...args),即执行middleware1(next,...args),此时的next为(...args) => middleware(next,...args)。- 执行
middleware1中的next,即执行middleware2(next,...args),此时的next为fetch - 执行
fetch(URL,data)
Generator包装中间件
generator是ES6新语法,通过 * 搭配 yield 实现。具体来说有三大特点:
*标识符用来定义生成器函数yield在生成器内部,是异步不同阶段的分界线,生成器函数会在每次调用yield时暂停执行,并在下一次迭代时从上次暂停的位置继续执行。可以理解成功能不太一样的return。- 相比起普通函数调用即执行函数题,生成器函数会生成一个迭代器对象。有两种常见方法对该对象进行迭代,一种是
for...of循环,一种是.next方法。
generator实现迭代简单例子:
// 生成器函数
function* fn(){
yield 'a'
yield 'b'
yield 'c'
return 'd end'
}
// 使用生成器函数创造一个迭代器对象
var _fn = fn()
// for...of迭代
for (const item of _fn) {
console.log(item);
}
// a
// b
// c
// .next 迭代
// 每个yield返回一个对象,包含两个值value,done
console.log(_fn.next())
console.log(_fn.next())
console.log(_fn.next())
console.log(_fn.next())
// {value: 'a', done: false}
// {value: 'b', done: false}
// {value: 'c', done: false}
// {value: 'd end', done: true}
需要注意的是,上述例子为了方便将两种迭代方式写在一起。实际过程中,迭代一次done的值已经为true,不能再进行第二次迭代。
使用generator进行中间件封装
function* middleWrapper(fetchFunc, middlewares) {
let currentIndex = middlewares.length - 1;
// 定义一个next函数,用于递归调用中间件
function* next(url, data) {
if (currentIndex >= 0) {
const currentMiddleware = middlewares[currentIndex];
currentIndex--;
// 使用yield*将控制权传递给下一个中间件
yield* currentMiddleware(next, url, data);
} else {
// 所有中间件执行完毕,调用最终的fetch函数
fetchFunc(url, data);
}
}
// 返回一个生成器函数,用于调用
wrappedFetch return function(url, data) {
const generator = next(url, data);
generator.next();
};
}
// 使用示例
const wrappedFetch = middleWrapper(fetch, middleArr);
wrappedFetch("https://example.com/api", { key: "value" });