Description
在co的README里,tj写道co是async/await的一块垫脚石
,没想到好时代这么快就来了,我们已经可以借助babel写出形似同步的异步代码了(需开启experimental模式)。而借着这股春风,koa2.0也来了。
我们还是先来看一下什么是async/await函数吧。
async函数就是generator + promise,只不过是将yield换成了await,但是比yield好理解,并且不用next来显式地执行下一步。async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果。我们来看一个例子(引用自阮一峰老师的ECMAScript 6 入门)。
const readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
const asyncReadFile = async function (){
const f1 = await readFile('a.txt');
const f2 = await readFile('b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
可以看到,async和promise是紧密结合在一起的,await的结果就是被修饰函数的resolve值,那似乎没有方法接收reject呀,所以,最好是将await包裹在try/catch中,用来接收reject。
const asyncReadFile = async function (){
try{
const f1 = await readFile('a.txt');
const f2 = await readFile('b.txt');
console.log(f1.toString());
console.log(f2.toString());
} catch(e) {
console.log('failed!');
}
};
有了async,能让我们更好地书写异步代码,就是这么的方便。
说了这么多async,我们再来看koa。koa2.0相对于1.0,转变体现在将var换成了let及const,使用箭头函数简化书写,不再支持generator,以及对于中间件的处理上。
中间件有一个很形象的比喻,就像一个洋葱,一个请求从最外面那一层进入洋葱,一路上进过一层层的中间件,到达洋葱心之后,请求完成了任务,又派了响应出去,带上需要返回的数据,一层层地返回最外面。而在进入出来的过程中,中间件会对请求和响应做『手脚』,比如对请求检验cookie,对响应加etag。那中间件是怎么知道来的到底是请求还是响应呢?其实中间件不需要知道,这是因为请求肯定早于响应,所以koa的中间件的做法就是将函数的上半部分用来处理请求,下半部分用来处理响应(这里只是打比方)。而区分『上半部分』和『下半部分』的分界线,在1.0是yield next(),在2.0中,就是await next()或是return next().then()。
说完原理,再来看koa-compose,最关键的只有一步
return Promise.resolve(fn(context, function next() {
return dispatch(i + 1)
}))
Promise.resolve的作用是将一个普通的对象转换成promise对象,防止上一步放回一个普通函数,造成中间件链路的断裂。
为了更好地理解如何使用中间件,我们来写一个访客记录的中间件。
首先分析一下需求,我们认为请求中带有visited
字段cookie的请求是来自曾经访问过本网站的游客,就在数据库中增加一次访问量,并将该条请求打个标记便于后续操作。若没有visited
cookie,就在响应增加visited
cookie。
module.exports = async function (ctx, next) {
ctx.countNum = await count();
const visited = ctx.cookies.get('visited') ? true : false;
await next();
if (!visited) {
setCookie(ctx, 'visited');
}
}
function count() {
const Count = new mongoose.model('count');
Count.add()
.then(function() {
return Count.findCountNum();
})
.then(function(num) {
return Promise.reslove(num);
});
}
function setCookie(ctx, name) {
ctx.cookies.set(name, 'what ever');
}