中间件封装:迭代 / generator

197 阅读4分钟

业务场景

在发起网络请求时,往往需要对请求进行预处理或后处理。例如:

  1. 路由过滤器:在发起请求之前,可以使用中间件来检查请求的URL,并根据特定条件重定向请求,例如将HTTP请求转换为HTTPS请求。
  2. 请求参数处理:用于修改请求参数,如添加自定义的请求头、验证请求参数的有效性或对请求体进行加密解密等操作。
  3. 错误处理:捕获请求过程中的错误,并进行适当的处理,例如记录日志、返回特定的错误响应或触发相应的错误处理逻辑。
  4. 缓存控制:实现请求结果的缓存控制,如检查缓存是否存在并有效,如果有效则直接返回缓存结果,否则再发送网络请求获取最新结果。
  5. 认证和授权:处理用户认证和授权逻辑,如检查用户的登录状态、验证访问权限或生成访问令牌等。
  6. 数据转换:对请求和响应的数据进行转换,如将请求的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的结果经过mid1mid2的处理?

迭代的方式包装中间件

一个简单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' }

通过闭包的方式包装中间件。 执行过程如下:

  1. 执行wrappedFetch = middleWrapper(fetch, middleArr);

    1. next = fetch middleware = middleware2 返回函数(...args) => middleware(next,...args)
    2. next = (...args) => middleware(next,...args) middleware = middleware1 返回函数(...args) => middleware(next,...args)
    3. 执行结果为 wrappedFetch = (...args) => middleware(next,...args)
  2. 调用wrappedFetch("``https://example.com/api``", { key: "value" })

    1. "``https://example.com/api``", { key: "value" }作为参数,箭头函数返回结果middleware(next,...args),即执行middleware1(next,...args),此时的next为 (...args) => middleware(next,...args)
    2. 执行middleware1中的next,即执行middleware2(next,...args),此时的next为fetch
    3. 执行fetch(URL,data)

Generator包装中间件

generator是ES6新语法,通过 * 搭配 yield 实现。具体来说有三大特点:

  1. * 标识符用来定义生成器函数
  2. yield在生成器内部,是异步不同阶段的分界线,生成器函数会在每次调用yield时暂停执行,并在下一次迭代时从上次暂停的位置继续执行。可以理解成功能不太一样的 return
  3. 相比起普通函数调用即执行函数题,生成器函数会生成一个迭代器对象。有两种常见方法对该对象进行迭代,一种是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" });