Skip to content

Latest commit

 

History

History
157 lines (123 loc) · 5.7 KB

async-function.md

File metadata and controls

157 lines (123 loc) · 5.7 KB

使用 async function 开发应用

async function 是 js 语言层面提供的异步解决方案,Nodejs v7.6以上已经支持。框架也默认支持了 async function,在所有支持 generator function 的地方都可以使用 async function 替代。

注意:在基于 async function 编写应用代码时,请确保你的代码运行在 Node.js 7.6+ 的版本上。

controller & service

controller 章节中,我们提供了 controller 的两种写法:基于类和普通方法,其中所有用 generator function 实现的地方都可以用 async function 来实现,代码逻辑没有任何变化,仅需要将 yield 语法改成 await 语法。 而 servicecontroller 一样,所有的异步方法都可以用 async function 替换文档中的 generator function。

举个例子,将 controller 文档中的示例改造成 async function 模式:

// 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 即可。

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;
  },
};

中间件

框架中所有的中间件,包括标准定义方式以及在路由中定义的中间件都可以通过 async function 来编写。但是和 generator function 格式的中间件稍有不同的是,中间件的参数列表变化了,和 Koa v2.x 一样:

  • 第一个参数为 ctx,代表当前请求的上下文,是 Context 的实例。
  • 第二个参数为 next,用 await 执行它来执行后续中间件的逻辑。
// 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 中使用。

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

    function* () {
      const tasks = [ task(1), task(2), task(3) ];
      let results;
      // 并行
      results = yield tasks;
      // 控制最大并发数为 2
      result = yield require('co-parallel')(tasks, 2);
    }
  • async function

    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 方法,灵活的运用它们配合 async function 能让代码更加具有可读性。


一个完整的例子可以参考 examples/hackernews-async