Koa (下一代web框架) 【Node.js进阶】

koa (中文网) 是基于 Node.js 平台的下一代 web 开发框架,致力于成为应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石;

利用 async 函数 丢弃回调函数,并增强错误处理,koa 没有任何预置的中间件,可快速的编写服务端应用程序。

切入点


一、核心概念

  • Koa Application(应用程序)
  • Context(上下文,可简化为 ctx)
  • Request(请求)、Response(响应)

二、初识 koa

bash 复制代码
# 创建一个 app-koa 的文件夹,作为 koa 工程根目录
mkdir app-koa

# 进入创建的文件夹
cd app-koa

# 初始化 package.json
npm init -y

# 安装 koa
npm i koa

Tips: 可以在 package.json 中查看安装的所有依赖

在工程目录里创建一个 app.js

js 复制代码
const Koa = require('koa') // 引入 koa
const app = new Koa() // 实例化 koa

app.use(ctx => {
  ctx.body = 'hello koa'
})

// 启动应用程序  参数:端口号
app.listen(3000)

在终端中使用 node app.js 命令【注意:执行命令时,终端的路径必须指向当前程序目录】

打开浏览器访问:http://localhost:3000 此时浏览器中就会输出 hello koa

上面代码虽然轻松实现了一个 web 服务器,但是返回的数据和所请求都是固定的;

并不适应真实的业务场景,比如:获取请求接口时的参数、方法、修改一次代码就要在终端中重新运行启动命令等;

由于使用的 node app.js 启动,所以每次更改都要重新启动,这样给我们开发带来了极大的不便利,所以我们可以使用一些第三方依赖来自动监听文件的变化并重新启动,开发环境可以使用 nodemon ,首先安装 npm i nodemon -D,也可以全局安装此依赖。生产环境的话可以使用 pm2 安装之后在package.jsonscripts 中添加启动方式。如下:

json 复制代码
{
  "name": "app-koa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "koa": "^2.15.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.7"
  }
}

在终端中执行命令: npm run dev 这样就不用我们每次修改都重新启动,执行之后就会在终端中提示,如下:

三、路由

1、安装依赖:@koa/router

@koa/router

bash 复制代码
npm i @koa/router

2、定义路由

  1. app.js 中引入 @koa/router ,然后再实例化

    js 复制代码
    const Router = require('@koa/router')
    const router = new Router({ prefix: '/api/v1' }) // 实例化的时候可以自定义一个接口前缀
  2. 注册 router

    js 复制代码
    app.use(router.routes()).use(router.allowedMethods())
  3. 定义接口

    js 复制代码
    router.get('/', async ctx => {
      ctx.body = {
        status: 200,
        message: 'hello @koa/router'
      }
    })
    
    router.get('/user', async ctx => {
      ctx.body = {
        status: 200,
        message: 'success',
        data: {
          nickname: 'Simon',
          age: 18,
          jobs: '前端攻城狮',
          skills: '搬砖'
        }
      }
    })

app.js 完整代码如下:

js 复制代码
const Koa = require('koa')
const Router = require('@koa/router')
const app = new Koa()
const router = new Router({ prefix: '/api/v1' }) // 添加接口前缀

router.get('/', async ctx => {
  ctx.body = {
    status: 200,
    message: 'hello @koa/router'
  }
})

router.get('/user', async ctx => {
  ctx.body = {
    status: 200,
    message: 'success',
    data: {
      nickname: 'Simon',
      age: 18,
      jobs: '前端攻城狮',
      skills: '搬砖'
    }
  }
})

app.use(router.routes()).use(router.allowedMethods())
// 启动应用程序  参数:端口号
app.listen(3000)

在浏览器中请求: http://localhost:3000/api/v1http://localhost:3000/api/v1/user,结果如下图:

四、中间件

中间件其实就是一个个函数,通过 app.use() 注册;

在 koa 中只会自动执行第一个中间件,后面的都需要我们自己调用,koa 在执行中间件的时候都会携带两个参数 context (可简化为ctx)和 nextcontext 是 koa 的上下文对象,next 就是下一个中间件函数;

这也就是洋葱模型,所谓洋葱模型,就是指每一个 Koa 中间件都是一层洋葱圈,它即可以掌管请求进入,也可以掌管响应返回。

换句话说:外层的中间件可以影响内层的请求和响应阶段,内层的中间件只能影响外层的响应阶段。

执行顺序按照 app.use() 的顺序执行,中间件可以通过 await next() 来执行下一个中间件,同时在最后一个中间件执行完成后,依然有恢复执行的能力。

即,通过洋葱模型,await next() 控制调用 "下游"中间件,直到 "下游"没有中间件且堆栈执行完毕,最终流回"上游"中间件。

下面这段代码的结果就能很好的诠释,示例:

js 复制代码
// app.js

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {
  console.log(`this is a middleware 1`)
  await next()
  console.log(`this is a middleware 1 end `)
})

app.use(async (ctx, next) => {
  console.log(`this is a middleware 2`)
  await next()
  console.log(`this is a middleware 2 end `)
})

app.use(async (ctx, next) => {
  console.log(`this is a middleware 3`)
  await next()
  console.log(`this is a middleware 3 end `)
})

app.listen(3000)

运行结果如下:

bash 复制代码
this is a middleware 1
this is a middleware 2
this is a middleware 3
this is a middleware 3 end
this is a middleware 2 end
this is a middleware 1 end

中间件是如何执行的?

js 复制代码
// 通过 createServer 方法启动一个 Node.js 服务
listen(...args) {
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

Koa 框架通过 http 模块的 createServer 方法创建一个 Node.js 服务,并传入 this.callback() 方法。

this.callback() 方法源码精简实现如下:

js 复制代码
function compose(middleware) {
  // 这里返回的函数,就是上文中的 fnMiddleware
  return function (context, next) {
    let index = -1
    return dispatch(0)

    function dispatch(i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      // 取出第 i 个中间件为 fn
      let fn = middleware[i]

      if (i === middleware.length) fn = next

      // 已经取到了最后一个中间件,直接返回一个 Promise 实例,进行串联
      // 这一步的意义是保证最后一个中间件调用 next 方法时,也不会报错
      if (!fn) return Promise.resolve()

      try {
          // 把 ctx 和 next 方法传入到中间件 fn 中,并将执行结果使用 Promise.resolve 包装
          // 这里可以发现,我们在一个中间件中调用的 next 方法,其实就是dispatch.bind(null, i + 1),即调用下一个中间件
          return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
          return Promise.reject(err)
      }
    }
  }
}

callback() {
  // 从 this.middleware 数组中,组合中间件
  const fn = compose(this.middleware);

  // handleRequest 方法作为 `http` 模块的 `createServer` 方法参数,
  // 该方法通过 `createContext` 封装了 `http.createServer` 中的 `request` 和 `response`对象,并将这两个对象放到 ctx 中
  const handleRequest = (req, res) => {
    const ctx = this.createContext(req, res);
    // 将 ctx 和组合后的中间件函数 fn 传递给 this.handleRequest 方法
    return this.handleRequest(ctx, fn);
  };

  return handleRequest;
}

handleRequest(ctx, fnMiddleware) {
  const res = ctx.res;
  res.statusCode = 404;
  const onerror = err => ctx.onerror(err);
  const handleResponse = () => respond(ctx);
  // on-finished npm 包提供的方法,该方法在一个 HTTP 请求 closes,finishes 或者 errors 时执行
  onFinished(res, onerror);
  // 将 ctx 对象传递给中间件函数 fnMiddleware
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

将 Koa 一个中间件组合和执行流程梳理为以下步骤:

  • 通过 compose 方法组合各种中间件,返回一个中间件组合函数 fnMiddleware
  • 请求过来时,会先调用 handleRequest 方法,该方法完成;
  • 调用 createContext 方法,对该次请求封装出一个 ctx 对象;
  • 接着调用 this.handleRequest(ctx, fnMiddleware) 处理该次请求。
  • 通过 fnMiddleware(ctx).then(handleResponse).catch(onerror) 执行中间件。

五、常见获取值的几种方式

1、在 params 中取值

js 复制代码
// 前端请求
await axios.post('http://localhost:3000/api/v1/user/1')

// 服务端接收
router.post('/user/:id',async ctx => {
  // 获取 url 的 id
  cosnt { id } = ctx.params;  // { id: 1 }
})

2、在 query 中取值

也就是获取问号后面的参数

js 复制代码
// 前端请求
await axios.post('http://localhost:3000/api/v1/user?name=Simon&age=18')

// 服务端接收
router.post('/user', async ctx => {
  // 获取 url 的 name & age
  const { name, age } = ctx.request.query; // { name: Simon, age: 18 }
})

3、获取 header 中的参数

js 复制代码
// 前端 请求接口时设置请求头
axios
  .post(
    'http://localhost:3000/api/v1/user?name=Simon&age=18',
    {
      headers: {
        Author: 'token'
      }
    }
    //......
  )
  .then(res => console.log('res:', res))

// 在服务端 获取
router.post('/user', async ctx => {
  // 获取 header 中的 Author
  const { Author } = ctx.request.header // { Author: 'token' }
})

4、 获取 body 中的数据

在服务端获取 body 中的一些数据只能用一些外部的插件;如:koa-bodykoa-bodyparser 等等。

就以 koa-body 为例,首先安装 npm i koa-body,再引入:

js 复制代码
// 前端发起请求有两种传参方式,一种是 json,另一种是 fromData;
// 以 json 为例
axios.post('http://localhost:3000/api/v1/user', {name: 'Simon', age: 18}).then(res => {
    console.log('res:', res)
});

// 服务端
// 引入 koa-body 插件
const body = require('koa-body);

// 然后在注册中间件:
app.use(body());

// 在服务端获取:
router.post('/user', async ctx => {
    const res = ctx.request.body; // { name: 'Simon', age: 18 }
});

六、创建 RESTful 接口

js 复制代码
const Koa = require('koa')
const Router = require('@koa/router')
const koaBody = require('koa-body')
const app = new Koa()
const router = new Router({ prefix: '/api/v1' })

router.get('/', async ctx => {
  ctx.body = {
    status: 200,
    message: 'hello @koa/router'
  }
})

router.get('/user', async ctx => {
  ctx.body = {
    status: 200,
    message: 'success',
    data: {
      query: ctx.query,
      nickname: 'Simon',
      age: 18,
      jobs: '前端攻城狮',
      skills: '搬砖'
    }
  }
})

router.get('/user/:id', async ctx => {
  const { id } = ctx.params
  ctx.body = {
    status: 200,
    message: 'success',
    data: {
      id,
      nickname: 'Simon',
      age: 18,
      jobs: '前端攻城狮',
      skills: '搬砖'
    }
  }
})

router.post('/user', async ctx => {
  const { name, age } = ctx.request.body
  ctx.body = {
    status: 200,
    data: {
      name,
      age
    }
  }
})

// koa-body 中间件的引入顺序必须在 @koa/router 之前,否则获取不了 post 请求携带的数据
app.use(koaBody()).use(router.routes()).use(router.allowedMethods())

app.listen(3000)

希望上面的内容对你的工作学习有所帮助!欢迎各位一键三连哦~

各位 加油!

✨ 原创不易,还希望各位大佬支持一下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下

👍 点赞,你的认可是我创作的动力! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

⭐️ 收藏,你的青睐是我努力的方向! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!

✏️ 评论,你的意见是我进步的财富! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

相关推荐
zhougl9961 小时前
html处理Base文件流
linux·前端·html
花花鱼1 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_1 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷6 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript