使用Promise在业务层面分配资源

395 阅读2分钟

背景

最近在写一个steam和一些交易平台的自动交易的机器人,涉及到一个很简单的业务逻辑:买卖操作都需要登录权限,若在进行操作时登录失效则需要先去登录再继续该逻辑并返回结果。要解决的问题如下:

  1. 同时多个操作都需要登录时怎么只让机器人登录一次然后通知这些操作?
  2. 如何在登录后继续操作(类似断点的概念)?

代码实现

在初期写下简单的代码如下:

class Robot {
  constructor() {
    this.loginStatus = 'LOG_OUT'
  }

  login() {
    return new Promise((gRes, gRej) => {
      // 模拟登录
      setTimeout(gRes, 10000)
    })
  }

  buy() {
    if (this.loginStatus === 'LOG_OUT') {
      // 等待登录后继续操作
      this.login()
        .then(() => {
          // 继续操作
         })
    } else {
      // 正常业务流程
    }
  }

  sell() {
    if (this.loginStatus === 'LOG_OUT') {
      // 等待登录后继续操作
        this.login()
          .then(() => {
            // 继续操作
           })
    } else {
      // 正常业务流程
    }
  }
}

在第一次毫无思考的情况下写下这个代码,看似没有问题,但是由于 loginStatus 是个公共资源其实应该是需要在被抢占后阻止其他操作顺利读取或修改该变量。该机器人会有大量的并发地调用 buysell 方法,如果有一时刻登录失效了,那就会导致事实上的login多次进行登录操作(代码中用setTimeout来模拟实际上是发请求登录),造成资源浪费,且每次返回的token不一致导致业务逻辑混乱。

自己想的解决办法其实也很差劲,分享出来如下:

class Robot {
  constructor() {
    this.isInLogin = false
    this.loginStatus = 'LOG_OUT'
    this.loginDep = []
  }

  login() {
    return new Promise((gRes, gRej) => {
      this.loginDep.push({resolve: gRes, reject: gRej})
      if (this.isInLogin) {
        return
      }
      this.isInLogin = true
      // 模拟登录
      setTimeout(() => {
        // 登录完成
        this.loginDep.forEach(({ resolve, reject }) => {
          resolve()
        })
        this.isInLogin = false
      }, 10000)
    })
  }

  buy() {
    if (this.loginStatus === 'LOG_OUT') {
      // 等待登录后继续操作
      this.login()
        .then(() => this.buy())
    } else {
      // 正常业务流程
    }
  }

  sell() {
    if (this.loginStatus === 'LOG_OUT') {
      // 等待登录后继续操作
      this.login()
        .then(() => this.sell())
    } else {
      // 正常业务流程
    }
  }
}

以上是第一版改版后的写法,在代码中运行良好,第二天又想到Promise的意思就是承诺,既然login是一个操作那login就是一个承诺。改版后如下:

class Robot {
  constructor() {
    this.loginStatus = 'LOG_OUT'
    this.loginPromise = null
  }

  login() {
    if (this.loginPromise) {
      return this.loginPromise
    }
    let loginPromise = new Promise((gRes, gRej) => {
      // 模拟登录
      setTimeout(() => {
        // 登录完成
        this.loginPromise = null
      }, 10000)
    })
    return loginPromise
  }

  buy() {
    if (this.loginStatus === 'LOG_OUT') {
      // 等待登录后继续操作
       this.login()
        .then(() => this.sell())
    } else {
      // 正常业务流程
    }
  }

  sell() {
    if (this.loginStatus === 'LOG_OUT') {
      // 等待登录后继续操作
      this.login()
        .then(() => this.sell())
    } else {
      // 正常业务流程
    }
  }
}

以上是感觉自己学到的地方,因为Promise大家都会用,怎么用来解决实际业务场景还是需要看个人的想法。如果大家有更好的实现方式请不吝赐教。

2020年大家恭喜发财呀!