我们先实现这个小案例

app.use(async (ctx,next)=>{console.log(ctx.request.req.url)console.log(ctx.req.url)console.log(ctx.request.url);console.log(ctx.url);console.log(ctx.request.req.path)console.log(ctx.req.path)console.log(ctx.request.path);console.log(ctx.path);
});
复制代码

koa中的ctx包括原生的res和req属性,并新加了request和response属性 ctx.req = ctx.request.req = req; ctx.res = ctx.response.res = res; ctx代理了ctx.request和ctx.response

和koa源码一样,我们将代码分为4个文件application.js, context.js, response.js, request.js

application.js:

let context = require('./context');
let request = require('./request');
let response = require('./response');
let stream = require('stream');
class Koa extends EventEmitter{constructor(){super();this.context = context;//将context挂载到实例上this.request = request;//将request挂载到实例上this.response = response;//将response挂载到实例上}use(fn){this.fn = fn;}//此方法,将原生的req挂载到了ctx和ctx.request上,将原生的res挂载到了ctx和ctx.response上createContext(req,res){// 创建ctx对象 request和response是自己封装的let ctx = Object.create(this.context);//继承contextctx.request = Object.create(this.request);//继承requestctx.response = Object.create(this.response);// 继承responsectx.req = ctx.request.req = req;ctx.res = ctx.response.res = res;return ctx;}handleRequest(req,res){// 通过req和res产生一个ctx对象let ctx = this.createContext(req,res);  //cxt具备四个属性request,response,req,res,后两个就是原生的req,res}listen(){let server = http.createServer(this.handleRequest.bind(this));server.listen(...arguments);}
}复制代码

那例子中的ctx.request.path ctx.path和ctx.request.url/ctx.url怎么实现? 我们知道path和url都是请求中的参数,因此可以通过req实现,request.js实现如下:

let url = require('url')
let request = {get url(){return this.req.url  },get path(){return url.parse(this.req.url).pathname}
}
module.exports = request;
复制代码

当访问ctx.request.url时,访问get url方法,方法中的this指的是ctx.request 这样ctx.request.url,ctx.request.path,ctx.request.query都实现了,那么ctx.url,ctx.path怎么实现呢?

koa源码中用了代理,原理代码context.js代码如下:

let proto = {}
//获取ctx.url属性时,调用ctx.request.url属性
function defineGetter(property,name) {proto.__defineGetter__(name,function () {return this[property][name]; })
}
//ctx.body = 'hello' ctx.response.body ='hello'
function defineSetter(property, name) {proto.__defineSetter__(name,function (value) {this[property][name] = value;})
}
defineGetter('request','path');// 获取ctx.path属性时,调用ctx.request.path属性
defineGetter('request','url');// 获取ctx.url属性时,调用ctx.request.url属性
module.exports = proto;
复制代码

现在就实现了例子中的功能,接下来如何获取ctx.body和设置ctx.body呢? 用ctx.response.boxy实现,然后再代理下即可。 response.js代码如下:

let response = {set body(value){this.res.statusCode = 200;this._body = value;// 将内容暂时存在_body属性上},get body(){return this._body}
}
module.exports = response;
复制代码

此时ctx.response.body = 'hello',就将hello放到了ctx.response的私有属性_body上,获取ctx.response.body ,就可以将‘hello’取出。 然后在 context.js代码中将body也代理上:

let proto = {}function defineGetter(property,name) {proto.__defineGetter__(name,function () {return this[property][name]; })
}function defineSetter(property, name) {proto.__defineSetter__(name,function (value) {this[property][name] = value;})
}
defineGetter('request','path');
defineGetter('request','url');
defineGetter('response','body');
defineSetter('response','body');
module.exports = proto;
复制代码

这样ctx.body就可以获取到了。

重点来了,如何实现koa 中间件呢?

class Koa extends EventEmitter{constructor(){super();this.context = context;this.request = request;this.response = response;this.middlewares = [];//增加middlewares属性}use(fn){this.middlewares.push(fn);//每次调用use 都将方法存起来}...handleRequest(req,res){// 通过req和res产生一个ctx对象let ctx = this.createContext(req,res);// composeFn是组合后的promiselet composeFn = this.compose(this.middlewares, ctx);composeFn.then(()=>{//渲染body到页面})}
}
复制代码

koa内部将每一个方法都包了一层promise,这样可以执行异步操作,这也是和express的重要差别。 返回的composeFn也是一个promise,这样就可以在then里面做内容渲染了。 那compose如何实现呢?代码很短,如下:

 compose(middlewares,ctx){function dispatch(index) {if (index === middlewares.length) return Promise.resolve();//防止溢出,返回一个promiselet fn = middlewares[index];return Promise.resolve(fn(ctx,()=>dispatch(index+1)));//为保证每个方法都是promise,这里在外面包了一层promise}return dispatch(0);//先执行第一个fn方法}
复制代码

然后渲染页面:需要判断是对象,是文件,是流,是字符串的情况:

res.statusCode = 404;//默认404
composeFn.then(()=>{//渲染body到页面let body = ctx.body;if (body instanceof stream) {//是流body.pipe(res);}else if(typeof body === 'object'){res.end(JSON.stringify(body));}else if(typeof body === 'string' || Buffer.isBuffer(body)){res.end(body);}else{res.end('Not Found');}}})
复制代码

页面错误如何处理呢? 例子:

app.use(async (ctx,next)=>{ctx.body = 'hello';throw Error('出错了')
});
app.on('error', function (err) {console.log(err)
})
app.listen(3000);
复制代码

我们知道每一个方法都被包成了promise,当出现错误时,错误会被catch捕获,因此可以添加catch方法,捕获错误

composeFn.then(()=>{let body = ctx.body;if (body instanceof stream) {body.pipe(res);}else if(typeof body === 'object'){res.end(JSON.stringify(body));}else if(typeof body === 'string' || Buffer.isBuffer(body)){res.end(body);}else{res.end('Not Found');}}).catch(err=>{ // 如果其中一个promise出错了就发射错误事件即可this.emit('error',err);res.statusCode = 500;res.end('Internal Server Error');
})
复制代码

转载于:https://juejin.im/post/5b378999e51d455883468329

node之koa核心代码相关推荐

  1. 一步步实现koa核心代码

    导语:实现简单的koa核心代码,便于理解koa原理.顺便学习koa代码的那些骚操作 简单分析koa 创建一个 http 服务,只绑一个中间件.创建 index.js /** index.js */ c ...

  2. Node.js的核心与红利

    唯有明晰历史,才能了然当下,预知未来.作者从历史角度解读Node.js,帮助读者透过猜忌和谣言,看清真实的Node.js,了解Node.js的核心与红利. 令人惴惴不安的Node.js 我们越来越频繁 ...

  3. justjavac:从辍学到成为Deno核心代码贡献者,我的十年编程生涯

    [CSDN 编者按]大学因学费而辍学,最困难时睡在公园长椅,有远见的母亲让他走上了编程之路,温柔智慧的妻子用爱与信任激励着他秉承初心.奋勇前行.编程十余年,他已在GitHub开源三百多个项目,项目总S ...

  4. 基于 Node.js + Koa 构建完整的 Web API (配置 ESLint 和使用 Airbnb 编码规范)

    主题内容:基于 Node.js + Koa 构建完整的 Web API (配置 ESLint 和使用 Airbnb 代码规范) 背景描述:上一篇 基于 Node.js + Koa 构建完整的 Web ...

  5. 莫队算法(普通莫队、带修莫队、树上莫队、不删除莫队)学习笔记【理解+套路/核心代码+例题及题解】

    一.理解 我的理解就是巧妙的暴力,利用双指针以及分块思想,巧妙的移动双指针,时间复杂度可以达到O(NlogN). 强推博客:写的又好又全.链接 二.套路 1.普通莫队 [1]核心代码 bool cmp ...

  6. OpenCORE原理和核心代码破解,增加AVI格式。

    OpenCORE原理和核心代码破解--1 一.OpenCORE整体结构 逻辑层主要由PVPlayer,PlayerDriver,PVPlayerEngine来组成,其中PVPlayer主要接受上层的操 ...

  7. 使用Node.js+Koa 从零开始写个人博客系统——后端部分(一)

    使用Node.js+Koa 从零开始写个人博客系统系列 提示:在此文章中你可以学习到的内容如下: 1 如何使用Koa快速搭建项目 2 对Koa的核心组件Koa-Route的简单使用 3 3层架构思想 ...

  8. 利用WxJava实现PC网站集成微信登录功能,核心代码竟然不超过10行

    最近网站PC端集成微信扫码登录,踩了不少坑,在此记录下实现过程和注意事项. 本文目录 一.微信开放平台操作步骤1.创建"网站应用"2.获取AppID和AppSecret二.开发指南 ...

  9. 融资 2000 万美元后,他竟将核心代码全开源,这……能行吗?

    立即报名:https://t.csdnimg.cn/KqnS 有这么一位"任性"的技术创业者: 2017 年,50 岁开始第三次创业,踏足自己从未深入涉及过的物联网大数据平台,敲下 ...

最新文章

  1. 稳扎稳打Silverlight(29) - 2.0Tip/Trick之Cookie, 自定义字体, 为程序传递参数, 自定义鼠标右键...
  2. mysql cpu 内存占用_MySQL占用内存与CPU过高测试与解决办法
  3. C语言#line预处理器
  4. 巧做辅助线计算三角形角的度数
  5. 如何从零开始开发一款嵌入式产品(20年的嵌入式经验分享学习)
  6. 仔细看看,您会发现需要改进的地方
  7. 【Python CheckiO 题解】Index Power
  8. 一份关于如何为回归任务选择机器学习算法指南
  9. 以下不属于计算机安全术语,基础知识(D).doc
  10. LeetCode 122 买卖股票的最佳时机 II
  11. java 快速排序 递归_Java递归快速入门
  12. 合并表格中同一列中相同的内容
  13. 相信美好就能遇见美好—西安独行三日短途穷游 攻略
  14. ubuntu/deepin安装配置mysql
  15. 普林斯顿算法课Part2第四周作业_Boggle
  16. GEF(Graphical Editing Framework)介绍
  17. 【图像去噪】基于三边滤波器实现图像去噪附matlab代码
  18. 【秋招面经】之神策数据
  19. 华为harmonyos 2.0,连接无限可能 华为HarmonyOS 2.0 正式发布
  20. 网页设计与制作(HTML+CSS)(二)

热门文章

  1. Android系统--输入系统(八)Reader线程_使用EventHub读取事件
  2. 实例具体解释Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(二)...
  3. poj 1088(记忆化搜索)
  4. 【Sublime Text 3】Sublime Text 3 - cracked 3092
  5. suse 10 下mysql安装
  6. 让FLASH背景透明-可运用于在网页内的FLASH内嵌入另一个网页
  7. 【python】Python的基本数据类型之运算符
  8. 【python】python的环境搭建
  9. JAVA通过HTTPS发送POST请求的方法
  10. 玩转JMETER参数化