vscode 调试用的运行环境是了Launch Program!!!其他环境就错误,虽然还不懂这块,先挖坑吧,累死我了。。。┭┮﹏┭┮
使用中间件
首先使用npm init -y 初始化项目,根目录下新建app.js入口文件,再npm install koa
app.use()里面一次装载中间件,设计中间件功能是在后台打印字符。通过打印出来的字符可以观察到请求再中间件内部的运动轨迹,就和洋葱模型一样。
中间件是回调函数,该函数传入两个参数 ctx, next
ctx 对象是对原生node.js接口的二级封装
当该中间件业务完成时或者需要等待后面的中间件处理业务时,这时候用 next() 调用下一个中间件。
//app.js
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
console.log('进入了第一个中间件');
next()
console.log('离开了第一个中间件');
})
app.use((ctx, next) => {
console.log('进入了第二个中间件');
next()
console.log('离开了第二个中间件');
})
app.use((ctx, next) => {
console.log('进入了第三个中间件');
next()
console.log('离开了第三个中间件');
})
app.listen(3000)
启动后台服务,node app。再打开postman
后台打印:
回头看koa的洋葱模型图:执行next()的过程很像函数调用的过程。在函数内部调用函数,会一直进入函数的内部作用域。当最内部的函数执行完毕后会依次退出。
而中间件最亮点是处理异步操作的。
const Koa = require('koa')
const app = new Koa()
function waitPromise(time) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
app.use(async(ctx, next) => {
console.log('进入了第一个中间件');
await waitPromise(1000)
await next()
console.log('离开了第一个中间件');
})
app.use(async(ctx, next) => {
console.log('进入了第二个中间件');
await waitPromise(5000)
await next()
console.log('离开了第二个中间件');
})
app.use(async(ctx, next) => {
console.log('进入了第三个中间件');
await waitPromise(2000)
await next()
console.log('离开了第三个中间件');
})
app.listen(3000)
写一个函数waitPromise,该函数返回一个promise对象,当到达指定时间后promise才可以完成。
修改三个中间件函数,在里面用waitPromise函数模拟异步操作,同时使用async,await语法在waitPromise函数内部的异步逻辑完成之前进行挂起等待,并且在离开当前中间件之前,挂起等待直到中间件完成业务逻辑。
重新去启动服务并且请求接口,可以看到执行顺序仍然符合洋葱模型,并且是在指定时间后完成的。
深入了解洋葱模型
思考以下问题:
- 在 app.use()中传入 async 函数就实现了了中间件,那么 app.use() 到底做了什么
- 在中间件函数内调用 await next() 进入下一个中间件,并等待它的promise完成,那么next到底是什么,为什么执行next就可以进入下一个中间件
从入口函数进行调试。用vscode的调试工具打断点行调试。
实例化koa的时候,实际上是在调用koa:application构造函数
this.middleware = []; 为存放中间件函数的数组,
接着继续执行,跳入第二个断点,app.use是koa:application的一个方法,首先确保中间件是一个函数,再将中间件函数push到定义的middleware
最后到下一个断点 app.listen,在 postman 里发出一个请求,继续运行程序,跳入到 listen 的代码块。
要知道 koa 只是基于 nodejs 之上的一个框架,所以无论 koa 做了什么,都由下层的 nodejs 去实现。
思考我们是如何去启动一个 nodejs 的应用的,是去使用 http.createServer(this.callback()) 启动一个应用。
我们处理的业务全都在 this.callback() 中,所以我们这里看到 koa 去调用 http.createServer(this.callback()) 启动一个 nodejs 后台。
而核心就在于 this.callback() 做了什么?
callback() 返回了一个 handleRequest 函数,这个函数包括了res和req两个参数。这与我们预期是符合的,因为http.createServe是会提供这两个对象的。在handleRequest 函数中会执行handleRequest 函数,并且传入两个参数:ctx,fn。
ctx: 从代码 const ctx = this.createContext(req, res);可以直观的了解到ctx就是基于nodejs提供的req和res的封装。
fn: 从代码 const fn = compose(this.middleware);看出handleRequest 仅仅就是给compose后的fn 传入一个ctx,然后执行一下。
看compose函数,在\node_modules\koa-compose\index.js
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
首先确保 middleware 是个函数数组,然后返回一个新的函数,这个函数是我们写的三个中间件的合体。实际上它做的事情就只有一件,就是去执行 dispatch(0)
在dispatch() 函数中,我们看到在返回值当中调用了中间件函数,给我们传入的的第二个参数next就是dispatch,只不过将他的参数绑定为i+1,这是为了让我们执行await next()时,能够执行到当前中间件的下一个中间件函数。
我们在 return dispatch(0) 打上断点。启动调试,并在postman上发出请求。此时vscode左上角查看变量,在闭包中可以看到middleware数组中就是app.use中传入的三个中间件函数
然后使用单步调试进入dispatch函数,在函数的开始,通过判断闭包中的index 和 i 值大小来辨别我们是否非法的在同一个中间件中调用了两次 dispatch 函数 。因为我们可以看到 index和 i 是有绑定赋值关系的,在给下个中间件传递dispatch函数时,程序将dispatch的参数绑定到了当前 i+1 ,这意味着当你从调用的中间件返回时,此时的index >= i ,如果在当前中间件继续执行 dispatch 就会报错。
运行到这一步,以及可以看到fn 已经从middleware 中取了出来,
继续执行,经过跳出操作进入到第一个中间件内部,执行到next后我们又重回到dispatch进行以上操作,直到 middleware数组被取完,fn:undefined
所以最后会返回一个空的Promise.resolve()