中间件与 Redux 与 Koa
compose 函数
/**
* 接受一组函数,返回一个函数,该函数从右向左执行传入的函数,函数执行的结果作为下一个函数的参数,返回最后一个函数执行结果
* reduce的数组如果空数组,且reduce参数没有初始值,会报错
* reduce的数组如果只有一个元素,且reduce参数没有初始值,会直接返回数组中的唯一元素
* @param {...any} fns 一组函数
*/
const compose = (...fns) =>
fns.reduce(
(prev, cur) =>
(...x) =>
prev(cur(...x))
)
简单的中间件实现
compose 的不足:只能内层执行完再执行外层,无法实现洋葱模型似的中间件
中间件接受一个 next 函数,返回一个函数 A。
A 可在 next 执行前做一些处理(修改参数等)
A 可在 next 执行后做一些处理(修改结果等)
applyMiddleware 接收初始函数,和中间件数组,通过类似 compose 的方式,返回一个加强后的函数
const middleware_1 =
next =>
(...a) => {
console.log('middleware_1 start', ...a)
const res = next(...a)
console.log('middleware_1 end', res)
return res
}
const middleware_2 =
next =>
(...a) => {
console.log('middleware_2 start', ...a)
const res = next(...a)
console.log('middleware_2 end', res)
return res
}
const doSth = (...args) => {
console.log('doSth', ...args)
return args
}
// 执行applyMiddleware时,立即传入基础函数
const applyMiddleware = (func, middlewares) =>
middlewares.reduce((prev, cur) => cur(prev), func)
// 延迟传入基础函数
const applyMiddleware_2 = middlewares =>
middlewares.reduce((prev, cur) => func => cur(prev(func)))
const last = applyMiddleware(doSth, [middleware_1, middleware_2])
const last_2 = applyMiddleware_2([middleware_1, middleware_2])(doSth)
console.log('last end', last('params'))
console.log('last_2 end', last_2('params_2'))
redux 中的中间件
从下面代码看到,redux 的中间件还要多包一层(传入{getState,dispatch}),通过
const chain = middlewares.map(middleware => middleware(middlewareAPI))
转化成我们上面的中间件形式然后 redux 在组合中间件时,采用了延迟传入基础函数的方式
compose(...chain)(store.dispatch)
中间件形式:({getState,dispatch})=>next=>action=>{...}
通过 applyMiddleware 和 compose 组合来加强 dispatch
function logger({ getState }) {
return next => action => {
console.log('will dispatch', action)
// Call the next dispatch method in the middleware chain.
const returnValue = next(action)
console.log('state after dispatch', getState())
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue
}
}
function applyMiddleware(...middlewares) {
return createStore =>
(...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
)
}
Koa 中的中间件
Koa 使用了koa-compose组合中间件
koa 中的中间件组合:返回一个函数,该函数从第一个中间件开始执行,执行中间件时传入下一个中间件的执行器(dispatch)。
// 中间件形式
app.use(async (ctx, next) => {
await next()
const rt = ctx.response.get('X-Response-Time')
console.log(`${ctx.method} ${ctx.url} - ${rt}`)
})
// 组合
function compose(middleware) {
if (!Array.isArray(middleware))
throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function')
throw new TypeError('Middleware must be composed of functions!')
}
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch(i) {
if (i <= index)
return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
为 fetch 添加中间件
保留 fetch 的原生语法,按需添加中间件,逻辑清晰
// fetch usage: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
// fetch可以接收一个参数或两个参数 fetch(url/Request,[options])
// 此处只考虑fetch(url,options) 的情况
// https://github.com/sindresorhus/is-plain-obj
function isPlainObject(value) {
if (Object.prototype.toString.call(value) !== '[object Object]') {
return false
}
const prototype = Object.getPrototypeOf(value)
return prototype === null || prototype === Object.prototype
}
const { fetch } = window
function fetchApi(url, options) {
return fetch(url, options)
}
const applyMiddleware = (func, middlewares) =>
middlewares.reduce((prev, cur) => cur(prev), func)
/**
* 1. 添加'Content-Type': 'application/json'
* 2. 将body stringify
* @param {*} next
* @returns
*/
const reqToJson = next => (url, options) => {
let { body, headers } = options
if (isPlainObject(body)) {
body = JSON.stringify(body)
} else {
throw new Error('options.body is not plainObject')
}
if (!headers) {
headers = {}
}
if (!headers['Content-Type']) {
headers = {
...headers,
'Content-Type': 'application/json',
}
} else {
throw new Error(
'headers with Content-Type is already set, and it\'s not "application/json"'
)
}
return next(url, { ...options, headers, body })
}
/**
* 为url添加前缀
* @param {*} baseUrl
* @returns
*/
const addPrefix = baseUrl => next => (url, options) => {
if (typeof baseUrl !== 'string') {
throw new Error('baseUrl should be string')
}
return next(`${baseUrl}${url}`, options)
}
const myFetch = applyMiddleware(fetchApi, [
reqToJson,
addPrefix('http://localhost:3001'),
])
export default myFetch
最后更新于
这有帮助吗?