10分钟学会 koa

news/2024/7/19 12:44:20 标签: javascript, koa, node.js, express, 后端

koa_0">认识 koa

express 的继任者,更轻,更小。

基本使用

koa注册的中间件提供了两个参数:

  • ctx:上下文(Context)对象;
  • koa并没有像express一样,将req和res分开,而是将它们作为ctx的属性;
  • ctx代表依次请求的上下文对象;
    • ctx.request:获取请求对象;
    • ctx.response:获取响应对象;
  • next:本质上是一个dispatch,类似于之前的next;

express 中如果没有在中间件中调用 end,请求会被挂起。而 koa 是所有中间件执行完就会返回 NOT FOUND 关闭连接。

没有 end,那 koa 怎么手动结束请求呢?通过 body。比如设置ctx.response.body = "请求结束"

javascript">const Koa = require('koa');

// 实例化
const app = new Koa();

app.use((ctx, next) => {
  ctx.response.body = "Hello World";
});

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

中间件

koa 通过创建的 app 对象,注册中间件只能通过 use 方法:
没有提供下面的注册方式:

  • methods方式: app.get('/.post')
  • path方式: app.use('/home', (ctx, next) => {})
  • 连续注册: app.use((ctx, next) => {}, (ctx, next) => {})

但是真实开发中我们如何将路径和 method 分离呢?
方式一:根据 request 自己来判断;
方式二:使用第三方路由中间件;

javascript">app.use((ctx, next) => {
  if (ctx.request.url === '/login') {
    if (ctx.request.method === 'GET') {
      console.log("来到了这里~");
      ctx.response.body = "Login Success~";
    }
  } else {
    ctx.response.body = "other request~";
  }
});

路由

koa官方并没有给我们提供路由的库,我们可以选择第三方库:koa-router
安装:npm i koa-router

koa-router 可以支持和 express 中的 use 一样,定义路径和连读定义中间件。

我们可以先封装一个 user.router.js 的文件:在app中将router.routes()注册为中间件:

javascript">const Router = require('koa-router');

// 实例化路由,并且添加公共路径前缀
const router = new Router({prefix: "/users"});

// router 中就可以和 express 一样使用 methods 的方式注册中间件
router.get('/', (ctx, next) => {
  ctx.response.body = "User Lists~";
});

router.put('/', (ctx, next) => {
  ctx.response.body = "put request~";
});

module.exports = router;

路由实例的 routes 方法注册路由中间件。

javascript">const Koa = require('koa');

const userRouter = require('./router/user');

const app = new Koa();

app.use(userRouter.routes());
app.use(userRouter.allowedMethods());

app.listen(8000, () => {
  console.log("koa路由服务器启动成功~");
});

当一些方法没有定义时,比如上面就没有注册 post 方法的中间件。如果客户端发起 post 的请求,返回的是 NOT FOUND。这不太合理。
那么就可以使用路由对象的 allowedMethods 方法。

allowedMethods 用于判断某一个 method 是否支持:

  • 如果我们请求 get,那么是正常的请求,因为我们有实现 get;
  • 如果我们请求 put、delete、patch,那么就自动报错:Method Not Allowed,状态码:405;
  • 如果我们请求 link、copy、lock,那么就自动报错:Not Implemented,状态码:501;

请求数据处理

params 和 query

因为 koa 的 use 中没有路径的写法,所以一般是在路由中处理请求数据。
params 和 query 数据都是可以在请求对象 request 中直接取。

javascript">const Koa = require('koa');

const app = new Koa();
const Router = require('koa-router');

const userRouter = new Router({prefix: '/users'});

userRouter.get('/:id', (ctx, next) => {
  console.log(ctx.request.params);
  console.log(ctx.request.query);
})

app.use(userRouter.routes());

app.listen(8000, () => {
  console.log("参数处理服务器启动成功~");
});

json 和 urlencoded

koa 中 ctx.response.body 也是 undefined 无法获取到请求体中的数据。所以需要借助第三方库:koa-bodyparser。之后就可以和 express 中一样直接在 body 中读取数据。
安装:npm i koa-bodyparser

而且它不仅可以解析 json,还可以解析 urlencoded 数据,但是无法解析 form-data。

javascript">const Koa = require('koa');
const bodyParser = require('koa-bodyparser');

const app = new Koa();

app.use(bodyParser());

app.use((ctx, next) => {
  console.log(ctx.request.body);
  ctx.response.body = "Hello World";
});

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

form-data

express 中解析 form-data 用的是 multer,koa 中当然也是用第三方库,用的是koa-multer
安装:npm i koa-multer

解析非文件数据

express 中不建议将 any 方法全局使用,尽量使用在 methods 中局部针对路径使用。koa 中也一样,尽量在路由中使用,koa 路由中也支持连续定义中间件。

虽然我们很少用 form-data 格式来上传普通数据,但是有一个大坑要注意:

  • 对于普通的 form-data 数据,koa-multer 没有将它放入ctx.request.body 中,而是放入了 ctx.req.body中!

req 和 request 有啥区别?

  • request 是 koa 自己实现的请求对象。req 是原生 nodej 中 http 模块的请求对象。
javascript">const Koa = require('koa');
const Router = require('koa-router')
const multer = require('koa-multer')

const app = new Koa();

const router = new Router({prefix: "/user"})

const upload = multer()

router.post("/", upload.any(), (ctx, next) => {
  console.log(ctx.req.body); // [Object: null prototype] { name: 'zs', age: '18' }
  console.log(ctx.request.body); // undefined
  ctx.response.body = "test";
})

app.use(router.routes())

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

文件上传

express 中使用 multer 是一样的。但是还是要注意,koa-multer 把数据全部写到 req 中了,不是 request。

javascript">const Koa = require('koa');
const Router = require('koa-router')
const multer = require('koa-multer')
const path = require("path")

const app = new Koa();

const uploadRouter = new Router({prefix: "/user"})

const storage = multer.diskStorage({
  destination: (req, file, cb) => { // 指定存储目录
    cb(null, './uploads');
  },
  filename: (req, file, cb) => { // 指定存储的文件名
    cb(null, Date.now() + path.extname(file.originalname)); // 时间戳+后缀
  }
})

const upload = multer({
  storage
})

uploadRouter.post("/", upload.single("pic"), (ctx, next) => {
  console.log(ctx.req.file); 
  ctx.response.body = "test";
})

app.use(uploadRouter.routes())

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

// {
//   fieldname: 'pic',
//   originalname: '@-?�-03.jpg',
//   encoding: '7bit',
//   mimetype: 'image/jpeg',
//   destination: './uploads',
//   filename: '1664906028636.jpg',
//   path: 'uploads\\1664906028636.jpg',
//   size: 214812
// }

数据的响应

输出结果:body 将响应主体设置为以下之一:

  • string :字符串数据
  • Buffer :Buffer数据
  • Stream :流数据
  • Object|| Array:对象或者数组(常用)
  • null :不输出任何内容

如果response.status尚未设置,Koa会自动将状态设置为200或204。

javascript">const Koa = require('koa');

const app = new Koa();

app.use((ctx, next) => {

  // 设置内容

  // ctx.response.body

  // ctx.response.body = "Hello world~"

  // ctx.response.body = {
  //   name: "zs",
  //   age: 18,
  //   avatar_url: "https://abc.png"
  // };

  // 设置状态码
  // ctx.response.status = 400;
  // ctx.response.body = ["abc", "cba", "nba"];

  // ctx.response.body = "Hello World~";
  ctx.status = 404;
  ctx.body = "Hello Koa~"; // 这种方式更简洁,实际背后还是执行 ctx.request.body
});

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

静态服务器

koa并没有内置部署相关的功能,所以我们需要使用第三方库:koa-static
安装:npm i koa-static

部署的过程类似于express

javascript">const Koa = require('koa');
const staticAssets = require('koa-static');

const app = new Koa();

app.use(staticAssets('./build'));

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

错误处理

app 中也支持事务总线。所以可以以事务总线的方式进行错误处理:让 app emit 发出错误,然后使用 on 监听。
context 中也可以拿到 app 对象,所以可以使用 ctx 去拿 app 对象。
为什么不直接使用 app 对象,因为一般都是在路由中操作,路由中不好拿 app 对象。

javascript">const Koa = require('koa');

const app = new Koa();

app.use((ctx, next) => {
  const isLogin = false;
  if (!isLogin) {
    // 发出错误
    ctx.app.emit('error', new Error("您还没有登录~"), ctx);
  }
});

// 监听错误
app.on('error', (err, ctx) => {
  ctx.status = 401;
  ctx.body = err.message;
})

app.listen(8000, () => {
  console.log("koa初体验服务器启动成功~");
});

koa__express__321">koaexpress 的区别

koaexpress 最大的区别就在于对 next 函数的实现不同。koa 中的 next 函数返回一个 promise,而 express 的 next 函数就是一个普通的函数。
这个区别导致它们再处理异步的时候有所区别。

另外 koa 中执行下一个中间件的函数其实是叫 dispatch,只是为了沿袭 express 中的习惯,我们使用的时候以 next 做参数名了。

中间件是怎么链式执行的

koaexpress 都一样,中间件的执行过程其实就相当于在递归调用。
如下:middleware1 中的 next 会去执行 middleware2 中的中间件函数,middleware2 中的 next 又会去执行 middleware3 中的中间件函数,最后返回到 middleware1 中。middleware1 中的 next 函数执行完毕,再继续往下执行 next 后面的代码 res.end。

javascript">const express = require('express');

const app = express();

const middleware1 = (req, res, next) => {
  req.message = "aaa";
  next();
  res.end(req.message); // aaabbbccc
}

const middleware2 = (req, res, next) => {
  req.message += "bbb";
  next();
}

const middleware3 = (req, res, next) => {
  req.message += "ccc";
}

app.use(middleware1, middleware2, middleware3);

app.listen(8000, () => {
  console.log("服务器启动成功~");
})

异步处理

koaexpress 一样的都是递归执行中间件,那如果中间件中发送了异步请求呢?该怎么拿到异步数据。
我们会发现,普通的中间件都是同步的,它们拿不到异步请求的结果。无论是 koa 还是 express

javascript">const express = require('express');
const axios = require('axios');

const app = express();

const middleware1 = (req, res, next) => {
  req.message = "aaa";
  next();
  res.end(req.message); // 结果为:aaabbb
}

const middleware2 = (req, res, next) => {
  req.message += "bbb";
  next();
}

const middleware3 = async (req, res, next) => {
  const result = await axios.get('http://localhost:9000'); // 注:服务器将返回 ccc
  req.message += result.data;
}

app.use(middleware1, middleware2, middleware3);

app.listen(8000, () => {
  console.log("express 服务器启动成功~");
})

express__386">express 获取异步请求的数据

不要将异步请求放在别的中间件中,就放在需要使用请求数据的中间件中。

javascript">const express = require('express');
const axios = require('axios');

const app = express();

const middleware1 = async (req, res, next) => {
  req.message = "aaa";
  next();
  const result = await axios.get('http://localhost:9000'); // 注:服务器将返回 ccc
  res.end(req.message + result.data); // 结果为:aaabbbccc
}

const middleware2 = (req, res, next) => {
  req.message += "bbb";
  next();
}

// const middleware3 = async (req, res, next) => {
//   const result = await axios.get('http://localhost:9000'); // 注:服务器将返回 ccc
//   req.message += result.data;
// }

app.use(middleware1, middleware2, middleware3);

app.listen(8000, () => {
  console.log("服务器启动成功~");
})

koa__417">koa 获取异步请求的数据

因为 koa 中每个 dispatch 函数返回的都是 promise,所以我们可以连续使用 await ,将中间件全链条进行 await。就不需要像 express 中改异步请求的位置了。

javascript">const Koa = require('koa');
const axios = require('axios');

const app = new Koa();

const middleware1 = async (ctx, next) => {
  ctx.message = "aaa";
  await next();
  ctx.body = ctx.message;
}

const middleware2 = async (ctx, next) => {
  ctx.message += "bbb";
  await next();
}

const middleware3 = async (ctx, next) => {
  const result = await axios.get('http://localhost:9000');
  ctx.message += result.data;
}

app.use(middleware1);
app.use(middleware2);
app.use(middleware3);

app.listen(8000, () => {
  console.log("koa 服务器启动成功~");
})

http://www.niftyadmin.cn/n/164395.html

相关文章

C++学习笔记 string篇

目录1. 连接字符串 2. 查找 find()3. 删除指定元素 erase() remove()4. 输出内容5. 一些注意的点 begin() end() size() empty()1. 连接字符串 string str1 "hello"; string str2 "world"; string str str1 str2;// "helloworld"2. 查找 …

【MD编辑器格式

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

聊聊华为的工作模式

目录 一、试用期与加班工资 二、招聘 三、月度答辩和转正答辩 四、可信考试认证 五、接口人 六、问题缺陷单 七、代码检视 八、功能开发 九、出征海外 一、试用期与加班工资 一般而言,试用期持续的时间为3-6个月,工资、奖金都按正式员工的标准…

Java的参数传递

最近一直疑惑一个问题,Java函数传递的形参会影响实参的值吗,听到网上很多说法,有的说java只有值传递,有的说java有值传递和引用传递,很迷糊。其实没有这么复杂,值传递、址传递、引用传递这些是c/c中的概念&…

阻塞式队列、定时器、线程池

1. JUC:多线程的很多类都在 java.util.concurrent 这个包里。 2.一个典型应用场景:生产者消费者模型 特点: 1)应用解耦 解耦 架构设计:高内聚低耦合 耦合:降低两个项目之间的关联程度 高内聚&#xf…

sdbusplus:service通过协程完成proxy

有的时候需要在service(记做s1)的method内访问其他service(记做s2)的method,如果s2的method需要较长的时间才能返回,那么通过普通的同步调用,会阻塞住s1,导致s1在此期间无法处理其他…

MySQL OCP888题解051-数据目录的权限

文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3.1、知识点1:MySQL对datadir的权限要求4、总结1、原题 1.1、英文原题 What is the purpose of changing ownership of datadir to the ‘mysql’ user?[1分] A、MySQL need…

零基础入门学习Python 07

目录 1.函数的参数 2.函数文档 3.返回值 4. 局部变量全局变量 5.内嵌函数 6.闭包 7.lambda表达式 8.BIF 1.函数的参数 多个参数逗号隔开: def 函数(参数1,参数2):print(参数1, 参数2)return(true) 形参实参 形参&#xff…