简单先说一下Go的defer特性:

  • defer是后进先出的原则执行
  • defer是在函数运行结束之后,才会运行

那么这些特性能干什么

  • 延迟任务
  • 尾部验证(比如运行函数之后,需要对返回值校验)
  • 资源销毁

一个具体的场景

在后端开发中,可能需要多人协作,多语言,多模块开发。这时候可能就需要用RPC通信

比如有个用户模块,在进入到一个业务的时候,你需要去验证用户是否合法

async function main(username) {
  const client = await rpc.createConnection(); // 创建RPC链接
  const data = await client.getUserInfo(username); // 通过RPC链接获取用户信息
  // 下面是业务逻辑
}

但是你发现,RPC链接并没有被关闭(即便函数执行完毕,对象client被销毁), 它依旧占用着服务端的资源,如果多次调用这个函数,会多服务端造成很大的压力.

解决方案有两个

  1. 维护连接池
  2. 在每次创建链接之后都销毁

第一种方法是大多数ORM,RPC框架做的,应用内只保持n个链接,不断的创建再销毁,而这个过程不用使用者关心。
很可惜的是,我选用的RPC框架Thrift,有屎一样的坑。为了保持和Go的业务逻辑一样,采用第二种方案,每次创建链接之后都销毁,保证它是干净的

依旧是上面的代码,改成这样

async function main(username) {
  const client = await rpc.createConnection(); // 创建RPC链接
  try{
    const data = await client.getUserInfo(username); // 通过RPC链接获取用户信息
    // 下面是业务逻辑
  }catch (err){
    await client.close(); // 关闭链接
    throw err
  }
}

上面的代码已经能很好的解决问题了,但是也有一个,如果需要关闭多种链接呢(比如RPC,DB,Socket等),一直try,catch也不是办法

受到Go的defer启发,于是写了这么一个库godefer , 依旧改写上面的代码

const godefer = require('godefer');

const main = godefer(async function(username, defer) {
  const client = await rpc.createConnection();

  // 注册一个defer函数
  defer(function() {
    client.close(); // 关闭链接
  });
  
  const data = await client.getUserInfo(username);
  
  // 下面是业务逻辑
  // ...
  // 运行完毕,则开始执行defer
});

Defer函数的执行,遵循了后进先出的原则, 即 先注册的defer函数,后运行

实现上与Go差不多, 有一点重要的差别是:

Go的defer内,能修改父级函数的返回值
godefer不能修改(不是不能做,而是不做)

好了,愉快的解决问题了, 有什么想法,评论来刚啊