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))
)
}
// 中间件形式
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 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