内容简介:最近读了 koa2 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍本文来自所有系列文章都放在了
最近读了 koa2 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计 和 3 个核心库,最终会 手动实现一个简易的 koa 。 这是系列第 2 篇,关于 3 个核心库的原理 。
本文来自 《心谭博客·深入koa源码:核心库原理》
所有系列文章都放在了 Github 。欢迎交流和Star ✿✿ ヽ(°▽°)ノ ✿
is-generator-function:判断 generator
koa2 种推荐使用 async 函数,koa1 推荐的是 generator。koa2 为了兼容,在调用 use
添加中间件的时候,会判断是否是 generator。如果是,则用 covert
库转化为 async 函数。
判断是不是 generator 的逻辑写在了 is-generator-function
库中,逻辑非常简单,通过判断 Object.prototype.toString.call
的返回结果即可:
function* say() {} Object.prototype.toString.call(say); // 输出: [object GeneratorFunction]
delegates:属性代理
delegates
和 koa 一样,这个库都是出自大佬 TJ 之手。它的作用就是属性代理。这个代理库常用的方法有 getter
, setter
, method
和 access
。
用法
假设准备了一个对象 target
,为了方便访问其上 request
属性的内容,对 request
进行代理:
const delegates = require("delegates"); const target = { request: { name: "xintan", say: function() { console.log("Hello"); } } }; delegates(target, "request") .getter("name") .setter("name") .method("say");
代理后,访问 request
将会更加方便:
console.log(target.name); // xintan target.name = "xintan!!!"; console.log(target.name); // xintan!!! target.say(); // Hello
实现
对于 setter
和 getter
方法,是通过调用对象上的 __defineSetter__
和 __defineGetter__
来实现的。下面是单独拿出来的逻辑:
/** * @param {Object} proto 被代理对象 * @param {String} property 被代理对象上的被代理属性 * @param {String} name */ function myDelegates(proto, property, name) { proto.__defineGetter__(name, function() { return proto[property][name]; }); proto.__defineSetter__(name, function(val) { return (proto[property][name] = val); }); } myDelegates(target, "request", "name"); console.log(target.name); // xintan target.name = "xintan!!!"; console.log(target.name); // xintan!!!
刚开始我的想法是更简单一些,就是直接让 proto[name] = proto[property][name]
。但这样做有个缺点无法弥补,就是之后如果 proto[property][name]
改变, proto[name]
获取不了最新的值。
对于 method
方法,实现上是在对象上创建了新属性,属性值是一个函数。这个函数调用的就是代理目标的函数。下面是单独拿出来的逻辑:
/** * * @param {Object} proto 被代理对象 * @param {String} property 被代理对象上的被代理属性 * @param {String} method 函数名 */ function myDelegates(proto, property, method) { proto[method] = function() { return proto[property][method].apply(proto[property], arguments); }; } myDelegates(target, "request", "say"); target.say(); // Hello
因为是“代理”,所以这里不能修改上下文环境。 proto[property][method]
的上下文环境是 proto[property]
,需要 apply
重新指定。
koa 中也有对属性的 access
方法代理,这个方法就是 getter
和 setter
写在一起的语法糖。
koa-compose:洋葱模型
模拟洋葱模型
koa 最让人惊艳的就是大名鼎鼎的“洋葱模型”。以至于之前我在开发 koa 中间件的时候,一直有种 magic 的方法。经常疑惑,这里 await next()
,执行完之后的中间件又会重新回来继续执行未执行的逻辑。
这一段逻辑封装在了核心库 koa-compose 里面。源码也很简单,算上各种注释只有不到 50 行。为了方便说明和理解,我把其中一些意外情况检查的代码去掉:
function compose(middleware) { return function(context) { return dispatch(0); function dispatch(i) { let fn = middleware[i]; try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err); } } }; }
middleware 里面保存的就是开发者自定义的中间件处理逻辑。为了方便说明,我准备了 2 个中间件函数:
const middleware = [ async (ctx, next) => { console.log("a"); await next(); console.log("c"); }, async (ctx, next) => { console.log("b"); } ];
现在,模拟在 koa 中对 compose 函数的调用,我们希望程序的输出是: a b c
(正如使用 koa 那样)。运行以下代码即可:
const fns = compose(middleware); fns();
ok,目前已经模拟出来了一个不考虑异常情况的洋葱模型了。
为什么会这样?
为什么会有洋葱穿透的的效果呢?回到上述的 compose
函数,闭包写法返回了一个新的函数,其实就是返回内部定义的 dispatch
函数。其中,参数的含义分别是:
- i: 当前执行到的中间件在所有中间件中的下标
- context: 上下文环境。所以我们在每个中间件中都可以访问到当前请求的信息。
在上面的测试用例中, fns
其实就是 dispatch(0)
。在 dispatch
函数中,通过参数 i 拿到了当前要运行的中间件 fn
。
然后,将当前请求的上下文环境(context)和 dispatch 处理的下一个中间件(next),都传递给当前中间件。对应的代码段是:
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
那么,在中间件中执行 await next()
,其实就是执行: await dispatch.bind(null, i + 1)
。因此看起来,当前中间件会停止自己的逻辑,先处理下一个中间件的逻辑。
因为每个 dispatch
,都返回新的 Promsise。所以 async
会等到 Promise 状态改变后再回来继续执行自己的逻辑。
async/await 改写
最后,在不考虑 koa 的上下文环境的情况下,用 async/await 的提炼出了 compose 函数:
function compose(middleware) { return dispatch(0); async function dispatch(i) { let fn = middleware[i]; try { await fn(dispatch.bind(null, i + 1)); } catch (err) { return err; } } }
下面是它的使用方法:
const middleware = [ async next => { console.log("a"); await next(); console.log("c"); }, async next => { console.log("b"); } ]; compose(middleware); // 输出a b c
希望最后这段代码能帮助理解!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入理解 FilterChainProxy【源码篇】
- 深入理解 WebSecurityConfigurerAdapter【源码篇】
- 深入koa2源码
- 深入理解channel:设计+源码
- 深入浅出Semaphore源码解析
- 深入剖析Vue源码 - 组件基础
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning Google Maps API 3
Gabriel Svennerberg / Apress / 2010-07-27 / $39.99
This book is about the next generation of the Google Maps API. It will provide the reader with the skills and knowledge necessary to incorporate Google Maps v3 on web pages in both desktop and mobile ......一起来看看 《Beginning Google Maps API 3》 这本书的介绍吧!