koa源码阅读之koa-compose/application.js
koa源码阅读之koa-compose/application.js
koa-Compose
为了理解方便特地把注释也粘进来
//这英语。我也来翻译一波
//大概就是把所有的中间件组合返回一个完整大块的中间件
/**
* 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)
}
}
}
}
如果你用过koa的话。现在你应该理解了next洋葱体的奥妙了。
application.js
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链式组装方式。都很强。
接下来文章的内容
继续阅读几个koa中间件。并且自己写点小demo在文章内,那么koa系列就结束啦。
如果这个文章帮助到你。
不妨给我一个赞/或者将这篇文章收藏起来。因为那是对我最好的鼓励
如果有什么错误的地方也不妨告诉我thanks
参考资料:
源码学习个人小空间
此处存放阅读源码学习的总结跟心得 有的可能是会参考他人结合自己总结 有的可能是自己阅读分析总结 有的...
推荐阅读
ctressa和linecitats
轮子造了就写文章以免遗忘咯。 ctressa ctressa是前一段时间对测试较为入迷。 所以阅读了一些断言库,断言框架的代码。 本质上断言框架例如 mocha,ava 这些,最初的实现大抵都是一个任务执行器。 每一个断言都是...
ZWkang阅读 1.1k
反编译微信小程序获取小程序前端源码wxapkg
研究反编译的原因就是我自己辛苦了半个月写的小程序,忘记备份放在桌面,心急体验Win11系统 重装系统忘记备份源码,后悔莫及。 后来网上找了反编译的教程,反编译已经上线了的小程序 于是自己尝试了一下,真的可...
TANKING赞 12阅读 10k评论 7
PDF 预览和下载你是怎么实现的?
在开发过程中要求对 PDF 类型的发票提供 预览 和 下载 功能,PDF 类型文件的来源又包括 H5 移动端 和 PC 端,而针对这两个不同端的处理会有些许不同,下文会有所提及。
熊的猫赞 7阅读 3.7k评论 1
Just for fun——C#应用和Nodejs通讯
进程通信常见的进程通讯的方法有:管道(Pipe)命名管道信号消息队列其他管道是比较简单基础的技术了,所以看看它。Node IPC支持Node官方文档中Net模块写着:IPC SupportThe net module supports IPC with named ...
pigLoveRabbit赞 3阅读 6.8k评论 2
「过程详解」async await综合题
如果你之前跟我一样一直对async await熟悉又陌生的话(熟悉是可能每天都在用,陌生是针对一些组合题又丈二和尚摸不着头脑),不妨可以边看边练,总结规律,相信会逐渐清晰并有所得。本文对每个案例都详细描述了代...
wuwhs赞 5阅读 1.2k
cligetter|一款快速生成 Cli工具 开发模版的脚手架
近年来 cli工具 的开发,对于不断发展的前端生态来说,似乎也逐渐成为工程师们的必备技能。其实开发一个 cli工具 并不难,但对于前端的同学可能存在一点认知上的小门槛,特别是对于刚开始接触 cli 脚手架工具开发...
木木剑光赞 3阅读 636
一个灵活的 Node.js 多功能爬虫库 —— x-crawl
x-crawl · x-crawl 是一个灵活的 Node.js 多功能爬虫库。灵活的使用方式和众多的功能可以帮助您快速、安全、稳定地爬取页面、接口以及文件。如果你也喜欢 x-crawl ,可以给 x-crawl 存储库 点个 star 支持一下,...
coderhxl赞 2阅读 1.9k评论 2
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。