mirror of
https://github.com/eggjs/egg.git
synced 2024-12-04 07:14:30 +00:00
158 lines
5.9 KiB
Markdown
158 lines
5.9 KiB
Markdown
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;
|
||
}
|
||
```
|
||
|
||
**注意:在上面的 controller 中,我们用 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
|