blog icon indicating copy to clipboard operation
blog copied to clipboard

【node实战系列】编写一个重试装饰器

Open yeyeye0525 opened this issue 3 years ago • 0 comments

file

本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。

【node实战系列】编写一个重试装饰器

背景

bigo前端开始推广bff,hello农场作为首个bff落地项目,历经2个月,完成了从0-1的落地实践。

【node实战系列】按照小模块拆分,从开发者的角度讲叙,如何进行bff高可用编码。

本系列文章,基于eggjs框架编码,使用ts语法,为了提升阅读体验,建议大家先了解一下eggjs。

系列文章

【node实战系列】编写一个重试装饰器 【node实战系列】自行实现应用缓存 【node实战系列】异步并发,自定义Promise.allSettled 【node实战系列】rpc与http协议通讯 【node实战系列】使用reqId跟踪请求全流程日志 【node实战系列】入参校验validate 【node实战系列】异常中断 【node实战系列】base64编码 【node实战系列】服务发现 【node实战系列】编码与约定 【node实战系列】监控告警 【node实战系列】单元测试 【node实战系列】压测 【node实战系列】灰度 【node实战系列】文档 【node实战系列】系列小结

欢迎大家关注我们的github blog,持续更新。 https://github.com/bigo-frontend/blog/issues

为什么要重试

这个很简单,项目开发中,调用第三方接口会因为网络延迟、异常导致调用的服务出错,重试几次可能就会调用成功(例如上传图片),所以需要一种重试机制进行接口重试来保证接口的正常执行。

为什么要用装饰器

其实我们可以在接口调用外面再包装一个重试方法,如下编码。

/**
 *
 * @param {number} retries - 重试次数
 * @param {Function} fn - 重试函数
 */
const retry = (retries, fn) => {
  fn().catch((err) => retries > 1 ? retry(retries - 1, fn) :  Promise.reject(err));
};

但是这种函数嵌套阅读起来不优雅,并且我们使用了ts进行编码,应该要物尽其用,发挥其装饰器能力。

装饰器简单来说就是对类或者其属性进行拓展,便于添加额外的功能。

什么是装饰器

先来看一下retry装饰器在代码中是长成什么样子吧

@retry(3, (res) => res.code !== 0)
public async initFarm() {
  const res = await this.request({
    opType: Eoperator.BASE_INIT_FARM,
  });
  return res;
}

代码中@retry(3, (res) => res.code !== 0)就是一个装饰器了

以 @ 作为标识符,可以作用于类,也可以作用于类的属性,具体使用原理请查看文档 https://www.tslang.cn/docs/handbook/decorators.html

方法装饰器

我们的重试装饰器就是一个方法装饰器,我们先讲解一下方法装饰器的各属性

下面是一个方法装饰器(@enumerable)的例子,应用于Greeter类的方法上:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting;
    }
}

我们可以用下面的函数声明来定义@enumerable装饰器:

function enumerable(value: boolean) {
    console.log(value); // 入参,false
    // target 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    // propertyKey 成员的名字
    // descriptor 成员的属性描述符
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      descriptor.enumerable = value;
    };
}

实现重试装饰器

// 休眠,延迟执行
function sleep(duration) {
  return new Promise((reslove) => setTimeout(reslove, duration))
}
/**
 * 重试装饰器
 *
 * @param {number} retries 重试次数
 * @param {*} cb 重试条件
 * @returns
 */
export function retry(retries: number, cb) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // 1. 保存原方法体
    const oldMethod = descriptor.value;
    // 2. 重新定义方法体
    descriptor.value = async function(...args) {
      // 重试
      const loop = async (count) => {
        // 3. 执行原来的方法体
        const res = await oldMethod.apply(this, args);
        if (count > 1 && cb(res)) {
          sleep(100); // 休眠100ms,再重试
          return loop(--count);
        } else {
          return res;
        }
      };
      return loop(retries);
    }
  }
}

小结

回顾全文,我们发现一个小而美的重试装饰器就实现了,希望大家也可以动手,封装自己的装饰器。

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。

yeyeye0525 avatar Jun 21 '21 07:06 yeyeye0525