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

file

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

背景

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,持续更新。
github.com/bigo-fronte…

为什么要重试

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

为什么要用装饰器

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

/**
 *
 * @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)就是一个装饰器了

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

方法装饰器

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

下面是一个方法装饰器(@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前端,下期见。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享