10分钟学会 koa

认识 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 = "请求结束"

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 自己来判断;
方式二:使用第三方路由中间件;

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()注册为中间件:

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 方法注册路由中间件。

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 中直接取。

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。

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 模块的请求对象。
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。

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。

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:

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 对象。

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 的区别

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

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

中间件是怎么链式执行的

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

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("服务器启动成功~");
})

异步处理

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

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 获取异步请求的数据

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

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 获取异步请求的数据

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

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 服务器启动成功~");
})

猜你喜欢

转载自blog.csdn.net/qq_43220213/article/details/129646204
koa