title: 使用 async function 开发应用 --- [前面的章节](../intro/egg-and-koa.md#async-function)中介绍了 [async function] 是 js 语言层面提供的异步解决方案,而 Node.js 从 7.6.0 开始将会升级 v8 到 5.5,届时 async function 将不再需要开启 flag 即可使用。框架也默认支持了 async function,在所有支持 generator function 的地方都可以使用 async function 替代。 **注意:在基于 async function 编写应用代码时,请确保你的代码运行在 Node.js 7.6+ 的版本上。** ## controller & service 在 [controller] 章节中,我们提供了 controller 的两种写法:基于类和普通方法,其中所有用 generator function 实现的地方都可以用 async function 来实现,代码逻辑没有任何变化,仅需要将 yield 语法改成 await 语法。 而 [service] 和 [controller] 一样,所有的异步方法都可以用 async function 替换文档中的 generator function。 举个例子,将 [controller] 文档中的示例改造成 async function 模式: ```js // app/controller/post.js module.exports = app => { class PostController extends app.Controller { // 从 * create() 换成 async create() async create() { const { ctx, service } = this; const createRule = { title: { type: 'string' }, content: { type: 'string' }, }; // 校验参数 ctx.validate(createRule); // 组装参数 const author = ctx.session.userId; const req = Object.assign(ctx.request.body, { author }); // 调用 service 进行业务处理 // 从 yield 换成 await const res = await service.post.create(req); // 设置响应内容和响应状态码 ctx.body = { id: res.id }; ctx.status = 201; } } return PostController; } ``` **注意:在上面的 contorller 中,我们用 await 调用了 `service.post.create()` 方法,如果这个方法是 generator function 类型,则也需要改造成 async function 接口才可以被调用。** ## 定时任务 框架提供的[定时任务]也支持 async function,只需要将 task 按照上面的规则,从 generator function 替换成 async function 即可。 ```js module.exports = { // 通过 schedule 属性来设置定时任务的执行间隔等配置 schedule: { interval: '1m', // 1 分钟间隔 type: 'all', // 指定所有的 worker 都需要执行 }, // task 是真正定时任务执行时被运行的函数,第一个参数是一个匿名的 Context 实例 async task(ctx) { const res = await ctx.curl('http://www.api.com/cache', { dataType: 'json', }); ctx.app.cache = res.data; }, }; ``` ## 中间件 框架中所有的中间件,包括[标准定义方式](../basics/middleware.md)以及在[路由中定义的中间件](../basics/router.md#中间件的使用)都可以通过 async function 来编写。但是和 generator function 格式的中间件稍有不同的是,中间件的参数列表变化了,和 Koa v2.x 一样: - 第一个参数为 `ctx`,代表当前请求的上下文,是 [Context](../basics/extend.md#Context) 的实例。 - 第二个参数为 `next`,用 await 执行它来执行后续中间件的逻辑。 ```js // app/middleware/gzip.js const isJSON = require('koa-is-json'); const zlib = require('zlib'); module.exports = (options, app) => { return async function gzip(ctx, next) { // 注意,和 generator function 格式的中间件不同,此时 next 是一个方法,必须要调用它 await next(); // 后续中间件执行完成后将响应体转换成 gzip const body = ctx.body; if(!body) return; // 支持 options.threshold if (options.threshold && ctx.length < options.threshold) return; if (isJSON(body)) body = JSON.stringify(body); // 设置 gzip body,修正响应头 ctx.body = zlib.createGzip().end(body); ctx.set('Content-Encoding', 'gzip'); }; }; ``` ## 调用 generator function API 由于一些已有的插件直接提供的是 generator function 的 API,无法直接在 async function 中通过 await 调用,我们可以通过 [co] 提供的 wrap 方法将它们包装成一个返回 Promise 的接口即可在 async function 中使用。 ```js const co = require('co'); app.mysql.query = co.wrap(app.mysql.query); // 如果想要直接用 query 方法,需要绑定 this 为 app.mysql const query = co.wrap(app.mysql.query).bind(app.mysql); // 包装之后即可在 async function 中使用 async function getUser() { // return await app.mysql.query(); return await query(); } ``` ## 和 generator function 的细微差别 尽管两者的编程模型基本一模一样,但是 [co] 做了一些特殊处理,例如支持 yield 一个数组(对象),这些在 async function 中都无法原生做到,但是基于一些 Prmoise 提供的方法以及工具库,也可以轻松的实现这些功能。 - generator function ```js function* () { const tasks = [ task(1), task(2), task(3) ]; let results; // 并行 results = yield tasks; // 控制最大并发数为 2 result = yield require('co-parallel')(tasks, 2); } ``` - async function ```js async () => { const tasks = [ task(1), task(2), task(3) ]; let results; // 并行 results = await Promise.all(tasks); // 控制最大并发数为 2 results = await require('p-all')(tasks, { concurrency: 2} ); } ``` @sindresorhus 编写了许多[基于 promise 的 helper 方法](https://github.com/sindresorhus/promise-fun),灵活的运用它们配合 async function 能让代码更加具有可读性。 ---- 一个完整的例子可以参考 [examples/hackernews-async](https://github.com/eggjs/examples/tree/master/hackernews-async)。 [async function]: https://github.com/tc39/ecmascript-asyncawait [co]: https://github.com/tj/co [controller]: ../basics/controller.md [service]: ../basics/service.md [定时任务]: ../basics/schedule.md