teambition
koa['kəʊə]
next generation web framework for node.js
Toa['təʊə]
pithy and powerful web framework for node.js
俗人造轮子,高手发明轮子,大神设计轮子
庸人混日子
var app = express()
app.use(function (req, res, next) {
/* ... */
next()
})
app.use(function (req, res, next) {
/* ... */
res.send('Hello World\n')
})
app.listen(3000)
express: req
, res
, next
var app = koa()
app.use(function *(next) {
/* this.req... */
/* this.res... */
yield next
/* will back */
})
app.use(function *(next) {
/* ... */
this.body = 'Hello World\n'
/* not res.send('Hello World\n') */
})
app.listen(3000)
koa: this
, next
, yield
app.createContext = function(req, res){
var context = Object.create(this.context);
var request = context.request = Object.create(this.request);
var response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
/* ... */
};
生成 context, request, response,
全部挂载 app 对象
var app = toa(function *() {
/* Body */
this.body = 'Hello World\n'
})
app.use(function *() {
/* ... */
/* this.req... */
/* this.res... */
})
app.listen(3000)
toa: this
, yield
三种异步原语:promise, thunk, generator
promise.then(function resolved (res) {
/* ... */
}, function rejected (err) {
/* ... */
})
各种 promise 实现之间,或 promise 与其它异步库之间无缝对接
promise.then(resolved).then(resolved).then(resolved).catch(rejected)
每一链返回新的 promise 对象,展平异步逻辑
var promiseX = promiseA.then(function (resA) {
/* ... */
return promiseB
}).then(function (resB) {
/* ... */
return promiseC
}).then(resolved)
乐高积木:任意多的 promise 组合成一个最终的 promise !
fs.readFile(filePath, function (err, data) {
if (err) return console.error(err)
console.log(data)
})
function readFile(path) {
/* 返回 thunk */
return function(callback) {
fs.readFile(path, callback)
}
}
CPS -> callback -> thunk
thunk(function (err, res, ...) {
/* ... */
})
thunk 是一个封装了特定任务的函数,运行该函数时任务才开始执行(惰性求值)
thunk(cb)(cb)(cb)(cb)(cb)(cb) /* thunk 链*/
thunk(1)(function (error, value) {
should(error).be.equal(null)
should(value).be.equal(1)
return Promise.resolve(2)
})(function (error, value) {
should(error).be.equal(null)
should(value).be.equal(2)
return thunk(x)
})(function (error, value) {
should(error).be.equal(null)
should(value).be.equal(x)
})(done)
thunk(function *() {
var p = yield promise
var t = yield thunk
var x = yield generator
/* ... */
})
co 或 thunks 异步库使 generator 函数成为了 JS 中最强大的异步流程控制工具
关于 promise, thunk, generator 的入门可参考
阮一峰老师的博客
app.callback = function(){
var mw = [respond].concat(this.middleware);
var fn = co.wrap(compose(mw));
var self = this;
return function(req, res){
res.statusCode = 404;
var ctx = self.createContext(req, res);
onFinished(res, ctx.onerror);
fn.call(ctx).catch(ctx.onerror);
}
};
koa / co 组合异步(同步)业务逻辑原理示意
proto.toListener = function () {
var body = this.body
var middleware = this.middleware.slice()
return function requestListener (req, res) {
var ctx = createContext(app, req, res, onerror, thunks({
debug: debug,
onstop: onstop,
onerror: onerror
}))
ctx.thunk.seq.call(ctx, middleware)(function () {
return body.call(this, this.thunk)
})(function () {
return this.thunk.seq.call(this, this.onPreEnd)(respond)
})
}
}
Toa / thunks 组合异步(同步)业务逻辑原理示意
issueAPI.createIssue = function *() {
if (this.token.hasRight < 0) this.throw(401)
var issue = yield this.parseBody()
issue = validateIssue(issue)
if (!issue.content) this.throw(422, 'Issue content required')
issue = yield issueDao.createIssue({
title: issue.title,
content: issue.content,
createdBy: this.token._id
})
this.body = yield fillCreatorAndUpdater(issue)
}
Teambition 用户社区代码示意
用同步代码书写异步情怀
userAPI.findOrCreate = function *(user) {
/* 从 cookie 解出的 user 信息*/
try {
user = yield userDao.findById(user._id)
} catch (err) {
/* 用户不存在,自动创建用户 */
user = yield userDao.create(user)
user.isNew = true
}
return yield findUserPreference(user)
}
主动 try catch 异常并处理,
或由 koa / Toa 自动处理
// koa-favicon
module.exports = function(path, options){
/* 省略代码 */
return function *favicon(next){
if ('/favicon.ico' != this.path) return yield next;
if (!path) return;
if ('GET' !== this.method && 'HEAD' !== this.method) {
this.status = 'OPTIONS' == this.method ? 200 : 405;
this.set('Allow', 'GET, HEAD, OPTIONS');
return;
}
if (!icon) icon = yield fs.readFile(path);
this.set('Cache-Control', 'public, max-age=' + (maxAge / 1000 | 0));
this.type = 'image/x-icon';
this.body = icon;
};
};
中间件模式:处理业务流程
// toa-token
module.exports = function toaToken(app, secretOrPrivateKeys, options) {
/* 省略代码 */
app.verifyToken = app.context.verifyToken = verifyToken
app.signToken = app.context.signToken = function (payload) {
return jwt.sign(payload, secretOrPrivateKeys[0], options)
}
app.decodeToken = app.context.decodeToken = function(token, options) {
return jwt.decode(token, options)
}
Object.defineProperty(app.context, 'token', {
enumerable: true,
configurable: false,
get: function () {/* 省略代码 */}
})
/* 省略代码 */
}
原型方法模式:提供便捷方法
app.context.someCtxMethod = someCtxMethod(options)
app.request.someReqMethod = someReqMethod(options)
app.response.someResMethod = someResMethod(options)
更安全的原型方法模式
// generator function
exports.someServiceA = function *(req) {
/*service code*/
}
// return thunk
exports.someServiceB = function (user) {
return function (callback) {
/*service code*/
}
}
// use it
var result1 = yield services.someServiceA(this.req)
yield services.someServiceA(this.state.user)
小模块组件模式,一个功能模块就是一个 generator 或 thunk 函数
Toa 推荐模式,提供仅需的参数即可,完全控制模块访问权限,更安全