百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

聊一聊koa2的一些事情(koa2是什么)

ccwgpt 2024-09-29 10:01 30 浏览 0 评论

koa的基础结构

首先,让我们认识一下koa框架的定位——koa是一个精简的node框架:

  • 它基于node原生req和res,封装自定义的request和response对象,并基于它们封装成一个统一的context对象。
  • 它基于async/await(generator)的洋葱模型实现了中间件机制。

koa框架的核心目录如下:

── lib
   ├── application.js
   ├── context.js
   ├── request.js
   └── response.js

// 每个文件的具体功能
── lib
   ├── new Koa()  || ctx.app
   ├── ctx
   ├── ctx.req  || ctx.request
   └── ctx.res  || ctx.response

koa源码基础骨架

application.js application.js是koa的主入口,也是核心部分,主要干了以下几件事情:

  1. 完成了koa实例初始化的工作,启动服务器
  2. 实现了洋葱模型的中间件机制
  3. 封装了高内聚的context对象
  4. 实现了异步函数的统一错误处理机制

context.js context.js主要干了两件事情:

  1. 完成了错误事件处理
  2. 代理了response对象和request对象的部分属性和方法

request.js request对象基于node原生req封装了一系列便利属性和方法,供处理请求时调用。所以当你访问ctx.request.xxx的时候,实际上是在访问request对象上的setter和getter。

response.js response对象基于node原生res封装了一系列便利属性和方法,供处理请求时调用。所以当你访问ctx.response.xxx的时候,实际上是在访问response对象上的setter和getter。

4个文件的代码结构如下:

koa工作流

Koa整个流程可以分成三步:

  1. 初始化阶段

new初始化一个实例,包括创建中间件数组、创建context/request/response对象,再使用use(fn)添加中间件到middleware数组,最后使用listen 合成中间件fnMiddleware,按照洋葱模型依次执行中间件,返回一个callback函数给http.createServer,开启服务器,等待http请求。结构图如下图所示:

undefined

  1. 请求阶段

每次请求,createContext生成一个新的ctx,传给fnMiddleware,触发中间件的整个流程。3. 响应阶段 整个中间件完成后,调用respond方法,对请求做最后的处理,返回响应给客户端。

koa中间件机制与实现

koa中间件机制是采用koa-compose实现的,compose函数接收middleware数组作为参数,middleware中每个对象都是async函数,返回一个以context和next作为入参的函数,我们跟源码一样,称其为fnMiddleware在外部调用this.handleRequest的最后一行,运行了中间件:fnMiddleware(ctx).then(handleResponse).catch(onerror);

以下是koa-compose库中的核心函数:

我们不禁会问:中间件中的next到底是什么呢?为什么执行next就进入到了下一个中间件了呢?中间件所构成的执行栈如下图所示,其中next就是一个含有dispatch方法的函数。在第1个中间件执行next时,相当于在执行dispatch(2),就进入到了下一个中间件的处理流程。因为dispatch返回的都是Promise对象,因此在第n个中间件await next()时,就进入到了第n+1个中间件,而当第n+1个中间件执行完成后,可以返回第n个中间件。但是在某个中间件中,我们没有写next(),就不会再执行它后面所有的中间件。运行机制如下图所示:

koa-convert解析

在koa2中引入了koa-convert库,在使用use函数时,会使用到convert方法(只展示核心的代码):

const convert = require('koa-convert');

module.exports = class Application extends Emitter {
    use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        if (isGeneratorFunction(fn)) {
            deprecate('Support for generators will be removed';
            fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || '-');
        this.middleware.push(fn);
        return this;
    }
}

koa2框架针对koa1版本作了兼容处理,中间件函数如果是generator函数的话,会使用koa-convert进行转换为“类async函数”。首先我们必须理解generatorasync的区别:async函数会自动执行,而generator每次都要调用next函数才能执行,因此我们需要寻找到一个合适的方法,让next()函数能够一直持续下去即可,这时可以将generatoryieldvalue指定成为一个Promise对象。下面看看koa-convert中的核心代码:

const co = require('co')
const compose = require('koa-compose')

module.exports = convert

function convert (mw) {
  if (typeof mw !== 'function') {
    throw new TypeError('middleware must be a function')
  }
  if (mw.constructor.name !== 'GeneratorFunction') {
    return mw
  }
  const converted = function (ctx, next) {
    return co.call(ctx, mw.call(ctx, createGenerator(next)))
  }
  converted._name = mw._name || mw.name
  return converted
}

首先针对传入的参数mw作校验,如果不是函数则抛异常,如果不是generator函数则直接返回,如果是generator函数则使用co函数进行处理。co的核心代码如下:

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);
  
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();
    
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

由以上代码可以看出,co中作了这样的处理:

  1. 把一个generator封装在一个Promise对象中
  2. 这个Promise对象再次把它的gen.next()也封装出Promise对象,相当于这个子Promise对象完成的时候也重复调用gen.next()
  3. 当所有迭代完成时,对父Promise对象进行resolve

以上工作完成后,就形成了一个类async函数。

异步函数的统一错误处理机制

在koa框架中,有两种错误的处理机制,分别为:

  1. 中间件捕获
  2. 框架捕获

中间件捕获是针对中间件做了错误处理响应,如fnMiddleware(ctx).then(handleResponse).catch(onerror),在中间件运行出错时,会出发onerror监听函数。框架捕获是在context.js中作了相应的处理this.app.emit('error', err, this),这里的this.app是对application的引用,当context.js调用onerror时,实际上是触发application实例的error事件 ,因为Application类是继承自EventEmitter类的,因此具备了处理异步事件的能力,可以使用EventEmitter类中对于异步函数的错误处理方法。

koa为什么能实现异步函数的统一错误处理?因为async函数返回的是一个Promise对象,如果async函数内部抛出了异常,则会导致Promise对象变为reject状态,异常会被catch的回调函数(onerror)捕获到。如果await后面的Promise对象变为reject状态,reject的参数也可以被catch的回调函数(onerror)捕获到。

委托模式在koa中的应用

delegates库由知名的 TJ 所写,可以帮我们方便快捷地使用设计模式当中的委托模式,即外层暴露的对象将请求委托给内部的其他对象进行处理。

delegates 基本用法就是将内部对象的变量或者函数绑定在暴露在外层的变量上,直接通过 delegates 方法进行如下委托,基本的委托方式包含:

  • getter:外部对象可以直接访问内部对象的值
  • setter:外部对象可以直接修改内部对象的值
  • access:包含 getter 与 setter 的功能
  • method:外部对象可以直接调用内部对象的函数

delegates 原理就是__defineGetter__和__defineSetter__。在application.createContext函数中,被创建的context对象会挂载基于request.js实现的request对象和基于response.js实现的response对象。下面2个delegate的作用是让context对象代理request和response的部分属性和方法:

undefined

做了以上的处理之后,context.request的许多属性都被委托在context上了,context.response的许多方法都被委托在context上了,因此我们不仅可以使用this.ctx.request.xxthis.ctx.response.xx取到对应的属性,还可以通过this.ctx.xx取到this.ctx.requestthis.ctx.response下挂载的xx方法。

我们在源码中可以看到,response.js和request.js使用的是get set代理,而context.js使用的是delegate代理,为什么呢?因为delegate方法比较单一,只代理属性;但是使用set和get方法还可以加入一些额外的逻辑处理。在context.js中,只需要代理属性即可,使用delegate方法完全可以实现此效果,而在response.js和request.js中是需要处理其他逻辑的,如以下对query作的格式化操作:

get query() {
  const str = this.querystring;
  const c = this._querycache = this._querycache || {};
  return c[str] || (c[str] = qs.parse(str));
}

到这里,相信你对koa2的原理实现有了更深的理解吧?

相关推荐

滨州维修服务部“一区一策”强服务

今年以来,胜利油田地面工程维修中心滨州维修服务部探索实施“一区一策”服务模式,持续拓展新技术应用场景,以优质的服务、先进的技术,助力解决管理区各类维修难题。服务部坚持问题导向,常态化对服务范围内的13...

谷歌A2A协议和MCP协议有什么区别?A2A和MCP的差异是什么?

在人工智能的快速发展中,如何实现AI模型与外部系统的高效协作成为关键问题。谷歌主导的A2A协议(Agent-to-AgentProtocol)和Anthropic公司提出的MCP协议(ModelC...

谷歌大脑用架构搜索发现更好的特征金字塔结构,超越Mask-RCNN等

【新智元导读】谷歌大脑的研究人员发表最新成果,他们采用神经结构搜索发现了一种新的特征金字塔结构NAS-FPN,可实现比MaskR-CNN、FPN、SSD更快更好的目标检测。目前用于目标检测的最先...

一文彻底搞懂谷歌的Agent2Agent(A2A)协议

前段时间,相信大家都被谷歌发布的Agent2Agent开源协议刷屏了,简称A2A。谷歌官方也表示,A2A是在MCP之后的补充,也就是MCP可以强化大模型/Agent的能力,但每个大模型/Agent互为...

谷歌提出创新神经记忆架构,突破Transformer长上下文限制

让AI模型拥有人类的记忆能力一直是学界关注的重要课题。传统的深度学习模型虽然在许多任务上取得了显著成效,但在处理需要长期记忆的任务时往往力不从心。就像人类可以轻松记住数天前看过的文章重点,但目前的...

不懂设计?AI助力,人人都能成为UI设计师!

最近公司UI资源十分紧张,急需要通过AI来解决UI人员不足问题,我在网上发现了几款AI应用非常适合用来进行UI设计。以下是一些目前非常流行且功能强大的工具,它们能够提高UI设计效率,并帮助设计师创造出...

速来!手把手教你用AI完成UI界面设计

晨星技术说晨星技术小课堂第二季谭同学-联想晨星用户体验设计师-【晨星小课堂】讲师通过简单、清晰的语言描述就能够用几十秒自动生成一组可编辑的UI界面,AIGC对于UI设计师而言已经逐步发展成了帮助我们...

「分享」一端录制,多端使用的便捷 UI 自动化测试工具,开源

一、项目介绍Recorder是一款UI录制和回归测试工具,用于录制浏览器页面UI的操作。通过UIRecorder的录制功能,可以在自测的同时,完成测试过程的录制,生成JavaScr...

APP自动化测试系列之Appium介绍及运行原理

在面试APP自动化时,有的面试官可能会问Appium的运行原理,以下介绍Appium运行原理。Appium介绍Appium概念Appium是一个开源测试自动化框架,可用于原生,混合和移动Web应用程序...

【推荐】一个基于 SpringBoot 框架开发的 OA 办公自动化系统

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍oasys是一个基于springboot框架开发的OA办公自动化系统,旨在提高组织的日常运作和管理...

自动化实践之:从UI到接口,Playwright给你全包了!

作者:京东保险宋阳1背景在车险系统中,对接保司的数量众多。每当系统有新功能迭代后,基本上各个保司的报价流程都需要进行回归测试。由于保司数量多,回归测试的场景也会变得重复而繁琐,给测试团队带来了巨大的...

销帮帮CRM移动端UI自动化测试实践:Playwright的落地与应用

实施背景销帮帮自2015年成立以来,移动端UI自动化测试的落地举步维艰,移动端的UI自动化测试一直以来都未取得良好的落地。然而移动互联网时代,怎样落地移动端的UI自动化测试以快速稳定进行移动端的端到端...

编写自动化框架不知道该如何记录日志吗?3个方法打包呈现给你。

目录结构1.loguru介绍1.1什么是日志?程序运行过程中,难免会遇到各种报错。如果这种报错是在本地发现的,你还可以进行debug。但是如果程序已经上线了,你就不能使用debug方式了...

聊聊Python自动化脚本部署服务器全流程(详细)

来源:AirPython作者:星安果1.前言大家好,我是安果!日常编写的Python自动化程序,如果在本地运行稳定后,就可以考虑将它部署到服务器,结合定时任务完全解放双手但是,由于自动化程序与平...

「干货分享」推荐5个可以让你事半功倍的Python自动化脚本

作者:俊欣来源:关于数据分析与可视化相信大家都听说自动化流水线、自动化办公等专业术语,在尽量少的人工干预的情况下,机器就可以根据固定的程序指令来完成任务,大大提高了工作效率。今天小编来为大家介绍几个P...

取消回复欢迎 发表评论: