node之koa核心代码

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

我们先实现这个小案例

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);//继承context
    ctx.request = Object.create(this.request);//继承request
    ctx.response = Object.create(this.response);// 继承response
    ctx.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是组合后的promise
    let 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();//防止溢出,返回一个promise
      let 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');
})


原文发布时间为:2018年06月30日
作者:芦梦宇
本文来源: 掘金  如需转载请联系原作者
相关文章
|
4月前
|
JavaScript 前端开发 算法
Node.js 艺术:用代码打印出绚丽多彩的控制台柱状图
Node.js 艺术:用代码打印出绚丽多彩的控制台柱状图
69 0
|
14天前
|
消息中间件 JavaScript 中间件
函数计算产品使用问题之WebIDE编写的Node.js代码是否会自动进行打包部署
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
2月前
|
缓存 JavaScript API
NodeJS代理配置指南:详细步骤和代码示例
**Node.js 代理配置:解决HTTP请求转发与CORS挑战** 在现代开发环境中,Node.js以其高效和灵活性深受青睐,但正确配置代理以处理跨域请求和API调用仍是复杂任务。本文提供全面指南,从基础到高级设置,教授如何在Node.js中使用代理,覆盖httpOptions、npm代理及第三方库的运用,以增强API调用灵活性。
NodeJS代理配置指南:详细步骤和代码示例
|
2月前
|
开发工具 git
vscode设置 git提交代码忽略node_modules,dist,vscode如何设置不提交node_modules,dist
vscode设置 git提交代码忽略node_modules,dist,vscode如何设置不提交node_modules,dist
162 0
|
4月前
|
前端开发 JavaScript 中间件
基于最新koa的Node.js后端API架构与MVC模式
基于最新koa的Node.js后端API架构与MVC模式
79 1
|
4月前
|
SQL 前端开发 JavaScript
前端vite+vue3结合后端node+koa——实现代码模板展示平台(支持模糊搜索+分页查询)
前端vite+vue3结合后端node+koa——实现代码模板展示平台(支持模糊搜索+分页查询)
127 4
|
4月前
|
安全 关系型数据库 MySQL
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
72 3
|
4月前
|
编解码 前端开发 JavaScript
node实战——koa实现文件下载和图片/pdf/视频预览(node后端储备知识)
node实战——koa实现文件下载和图片/pdf/视频预览(node后端储备知识)
179 1
|
4月前
|
存储 缓存 NoSQL
node实战——koa给邮件发送验证码并缓存到redis服务(node后端储备知识)
node实战——koa给邮件发送验证码并缓存到redis服务(node后端储备知识)
62 0
|
4月前
|
设计模式 测试技术
在实现链表的代码中,为什么要使用`Node`类而不是直接在`LinkedList`类中定义节点?
在实现链表的代码中,为什么要使用`Node`类而不是直接在`LinkedList`类中定义节点?
37 1