最近好久没更新了,工作太忙 + 感冒,anyway 总有不更新的理由。。。
最近喜欢读源码了,不得不说,读源码可以快速的把要用到的框架/库,一次性的吃透,而且比看文档理解的更透彻、更踏实!
当然我也没有大佬那种魄力,读什么 Vue, React 这种大型框架,仅仅简单瞅瞅,不过多深入,用到哪里看到哪里。
今天先从 koa2 以前要用到的转换 Generator function
的工具函数:convert
看起吧!
Koa-convert:koajs/convert: Convert koa generator-based middleware to promise-based middleware (github.com)
解决什么问题?
在node 7.6版本以下,是不支持async function
的,但如果你想在koa里优雅的处理当前任务需要上一个异步任务的完成
的情况时,通常是使用Generator function
(当然你也可以配置一些 polyfill)
Usage
const Koa = require('koa') // koa v2.x
const convert = require('koa-convert')
const app = new Koa()
app.use(modernMiddleware)
app.use(convert(legacyMiddleware))
function * legacyMiddleware (next) {
// before
yield next
// after
}
function modernMiddleware (ctx, next) {
// before
return next().then(() => {
// after
})
}
在以前(koa v1.x)的时候,使用 convert
来转换 Generator function
。
源码
convert
Convert legacy generator middleware to modern promise middleware.
将传统生成器式中间件
转换成现代 Promise 式中间件
/**
* Convert Koa legacy generator-based middleware
* to modern promise-based middleware.
*
*
* @api public
* */
function convert (mw) {
// mw 必须是函数
if (typeof mw !== 'function') {
throw new TypeError('middleware must be a function')
}
// assume it's Promise-based middleware
// 普通函数可以直接返回了
if (
mw.constructor.name !== 'GeneratorFunction' &&
mw.constructor.name !== 'AsyncGeneratorFunction'
) {
return mw
}
// 实际执行的中间件
const converted = function (ctx, next) {
// co 是一个 async 语法糖实现
return co.call(
ctx,
// 传入的 mw 是 Generator function 执行后返回 Generator Object
mw.call(
ctx,
(function * (next) { return yield next() })(next)
))
}
converted._name = mw._name || mw.name
return converted
}
短短几行代码而已,co 调用时会传入 ctx ,这个上下文对象是会真正的传入 待转换Generator function
的,作为 this 指针,指向 ctx。而 mw.call(ctx, ...)
传入的 ctx 在第一次执行 Generator function
的时候绑定到 this 指针上。但这一特性对箭头函数无效,koa2 更提倡使用箭头函数
签名已更改为通过
ctx
取代this
显式参数传递Context
。上下文传递更改使得 koa 更能兼容 es6 的箭头函数,通过捕获 “this”。
—— koa2 中文文档
convert.back
Convert modern promise middleware back to legacy generator middleware.
将现代 Promise 式中间件
转回成传统生成器式中间件
/**
* Convert Koa modern promise-based middleware
* to legacy generator-based middleware.
*
*
* @api public
* */
convert.back = function (mw) {
if (typeof mw !== 'function') {
throw new TypeError('middleware must be a function')
}
// assume it's generator middleware
// 如果已经是生成器函数则不必转换
if (mw.constructor.name === 'GeneratorFunction'
|| mw.constructor.name === 'AsyncGeneratorFunction') {
return mw
}
// 实际转换成的函数
const converted = function * (next) {
const ctx = this
let called = false
yield mw(ctx, function () {
if (called) {
// guard against multiple next() calls
throw new Error('next() called multiple times')
}
called = true
/* 如果返回值不是普通变量
* co 将会一直执行下去
*/
return co.call(ctx, next)
})
}
converted._name = mw._name || mw.name
return converted
}
convert.back
是 convert
的逆运算,不明白有何意义?
总结
本来以为简简单单的几行代码,实际写完还是有些许不理解,先记录下来,有新的收获再谈
Q1:convert
返回的函数中,为何绑定两次this,给mw绑定的ctx什么时候会用到,有实际意义吗?
Q2:convert.back
返回的函数中,为什么要判断是否 called ? next 执行两次在什么情况会触发?