常见扩展方法实现
拦截器
示例:axios
实现
// https://github.com/axios/axios/blob/master/lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
});
return this.handlers.length - 1;
};
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
// https://github.com/axios/axios/blob/master/lib/core/Axios.js
function Axios(instanceConfig) {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
Axios.prototype.request = function request(config) {
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
拦截器的注册:在 InterceptorManager
实例中使用一个数组储存拦截器函数,use
拦截器时会将 handler 插入数组,返回插入的位置,以备 eject
时使用。
拦截器的调用:Axios 的实例会为 request
和 response
生成两个独立的 InterceptorManager
。发起请求时,会生成一个 chain
的队列,将发起请求的 handler 放入其中,将 request 的拦截器插入队列前,将 response 的拦截器插入队列后,通过 Promise 链式调用的方式使这些 handler 可以依次被执行。
中间件
示例:koa
// https://github.com/koajs/koa/blob/master/lib/application.js
class Application extends Emitter {
constructor(options) {
super();
this.middleware = [];
}
/**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {Function} fn
* @return {Application} self
* @api public
*/
use(fn) {
if (typeof fn !== "function")
throw new TypeError("middleware must be a function!");
debug("use %s", fn._name || fn.name || "-");
this.middleware.push(fn);
return this;
}
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback() {
const fn = compose(this.middleware);
if (!this.listenerCount("error")) this.on("error", this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
/**
* Handle request in callback.
*
* @api private
*/
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = (err) => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
}
// https://github.com/koajs/compose/blob/master/index.js
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
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!");
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
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);
}
}
};
}
Application
实例使用 middleware
数组存储所有 use
的中间件函数。这些中间件函数会被 compose
后作为 request
的 callback 函数被调用。