爲了理解方便特意把註釋也粘進來node
//這英語。我也來翻譯一波 //大概就是把全部的中間件組合返回一個完整大塊的中間件 /** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */ //開始他的魔法 // middleware傳入的是一個數組 返回的是一個函數 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 */ // 上下文對象 返回一個promise // 注意上面的所謂返回函數其實是return這個匿名函數。這個匿名函數返回一個promise // tj對於異步回調的處理方案太強了 return function (context, next) { // last called middleware # let index = -1 //初始下標爲-1 return dispatch(0) function dispatch (i) { // 若是傳入i爲負數且<=-1 返回一個Promise.reject攜帶着錯誤信息 // 這裏也就是確保了next只調用一次 // 執行一遍next以後,這個index值將改變,由於下面有一個賦值 // 因此執行兩次next會報出這個錯誤。將狀態rejected if (i <= index) return Promise.reject(new Error('next() called multiple times')) // index = i; index = i // 取出一箇中間件 let fn = middleware[i] // 這塊處理在application中能夠發現。實際上next是個undefined // 也就是在Promise鏈式最深處是一個Promise.resolve if (i === middleware.length) fn = next //若是中間件遍歷到最後了。那麼。此時咱們最後return Promise.resolve() // 這個Promise reloved狀態 if (!fn) return Promise.resolve() // try catch保證錯誤在Promise的狀況下可以正常被捕獲。 // 這是一個遞歸。 // 咱們知道Promise.resolve的狀態是resolve // 而當Promise.resolve(value) value爲一個promise的時候。 // 返回的是傳入的這個promise // var d = Promise.reject('test') // var z = Promise.resolve(d) // z == d try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
const isGeneratorFunction = require('is-generator-function'); const debug = require('debug')('koa:application'); const onFinished = require('on-finished'); const response = require('./response'); const compose = require('koa-compose'); const isJSON = require('koa-is-json'); const context = require('./context'); const request = require('./request'); const statuses = require('statuses'); const Cookies = require('cookies'); const accepts = require('accepts'); const Emitter = require('events'); const assert = require('assert'); const Stream = require('stream'); const http = require('http'); const only = require('only'); const convert = require('koa-convert'); const deprecate = require('depd')('koa'); //頭部的引入模塊 前面的文章有所描述 /** * Expose `Application` class. * Inherits from `Emitter.prototype`. */ // appliaction繼承了nodejs 的Events模塊 module.exports = class Application extends Emitter { /** * Initialize a new `Application`. * * @api public */ // 初始化 constructor() { super(); this.proxy = false; this.middleware = []; this.subdomainOffset = 2; // 這個很常見,區分開發仍是線上模式 this.env = process.env.NODE_ENV || 'development'; //前面幾篇講過的文件 this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } /** * Shorthand for: * * http.createServer(app.callback()).listen(...) * * @param {Mixed} ... * @return {Server} * @api public */ // 這塊的用了個小細節。 // http.createServer(app.callback()).listen(...) // 其實就是上面這個的形式 listen(...args) { debug('listen'); //callback是個很主要的點。 const server = http.createServer(this.callback()); return server.listen(...args); } /** * Return JSON representation. * We only bother showing settings. * * @return {Object} * @api public */ toJSON() { return only(this, [ 'subdomainOffset', 'proxy', 'env' ]); } /** * Inspect implementation. * * @return {Object} * @api public */ inspect() { return this.toJSON(); } /** * Use the given middleware `fn`. * * Old-style middleware will be converted. * * @param {Function} fn * @return {Application} self * @api public */ // 咱們使用koa的時候. // var koa = require('koa')(); // koa.use(async function(context,next){}) // 這樣子就至關因而一個簡單的中間件了。 use(fn) { // 判斷傳入類型 須要是函數 if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); // 若是是generator函數的話,那麼咱們應該使用conver轉化一下。 // 上一篇個人文章就有提到這個庫。這裏不提了 if (isGeneratorFunction(fn)) { deprecate('Support for generators will be removed in v3. ' + 'See the documentation for examples of how to convert old middleware ' + 'https://github.com/koajs/koa/blob/master/docs/migration.md'); fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); //在this.middleware壓入這個函數 // 這裏請聯想一下koa-compose是傳入什麼的。 this.middleware.push(fn); // 提供鏈式調用 return this; } /** * Return a request handler callback * for node's native http server. * * @return {Function} * @api public */ // 當callback用在createServer回調內。那麼應該是一個function(req,res)的函數 callback() { const fn = compose(this.middleware); // 使用compose處理咱們的middleware中間件數組 // 返回一個promise if (!this.listeners('error').length) this.on('error', this.onerror); // 唔 就是這個handleRequest const handleRequest = (req, res) => { // 默認設置statuscODE res.statusCode = 404; // 建立的上下文對象 const ctx = this.createContext(req, res); // onerror函數 const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); //這個也是說過的啦 onFinished(res, onerror); // compose middlewares數組返回一個promise // 由於咱們內部有trycatch顯式地拋出狀態,因此在鏈上能夠catch // fn(ctx,undefined); return fn(ctx).then(handleResponse).catch(onerror); }; return handleRequest; } /** * Initialize a new context. * * @api private */ //建立咱們中間件用的ctx對象 createContext(req, res) { //下面是一波錯綜複雜的交替傳遞引用 const context = Object.create(this.context); const request = context.request = Object.create(this.request); const response = context.response = Object.create(this.response); // context ---app--->request ---app--->response---app --->this // ---req--->request ---req--->response---req --->req(原生可讀流) // ---res--->request ---res--->response---res --->res(原生可寫流) // ---ctx--->response---ctx---->context // ---response->response // response--request--->request // 這塊注意區分req request res response就能夠了 context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res; request.ctx = response.ctx = context; request.response = response; response.request = request; // 下面是一些屬性方便調用吧 // 並且還記得咱們的context.js文件裏面有一大堆屬性方法的委託吧。那麼意味着咱們能夠直接 // ctx.body==>ctx.response.body context.originalUrl = request.originalUrl = req.url; context.cookies = new Cookies(req, res, { keys: this.keys, secure: request.secure }); request.ip = request.ips[0] || req.socket.remoteAddress || ''; context.accept = request.accept = accepts(req); context.state = {}; return context; } /** * Default error handler. * * @param {Error} err * @api private */ // 默認錯誤處理 onerror(err) { assert(err instanceof Error, `non-error thrown: ${err}`); if (404 == err.status || err.expose) return; if (this.silent) return; const msg = err.stack || err.toString(); console.error(); console.error(msg.replace(/^/gm, ' ')); console.error(); } }; /** * Response helper. */ // 能夠想成res響應體輔助 function respond(ctx) { // allow bypassing koa if (false === ctx.respond) return; const res = ctx.res; if (!ctx.writable) return; let body = ctx.body; const code = ctx.status; // ignore body if (statuses.empty[code]) { // strip headers ctx.body = null; return res.end(); } if ('HEAD' == ctx.method) { if (!res.headersSent && isJSON(body)) { ctx.length = Buffer.byteLength(JSON.stringify(body)); } return res.end(); } // status body if (null == body) { body = ctx.message || String(code); if (!res.headersSent) { ctx.type = 'text'; ctx.length = Buffer.byteLength(body); } return res.end(body); } // responses // 三種處理 buffer 字符串 流 if (Buffer.isBuffer(body)) return res.end(body); if ('string' == typeof body) return res.end(body); if (body instanceof Stream) return body.pipe(res); // body: json // bodyJSON字符串序列化 handle ctx.body是對象 body = JSON.stringify(body); if (!res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body); }
主要的點在於koa-compose的包裝非常重要。
感受tj在這方面的功底真的太強了。
異步,next鏈式組裝方式。都很強。git