3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Promiseとメソッドの組み合わせで事故るやつをなんとかする

Posted at

Promiseとthisで事故るやつをなんとかする

Promiseとメソッドの組み合わせでthisが変わって事故るやつ
そもそも非同期処理の結果がthisの値によって変わること自体がよろしくないのかもと思い、かいてます
なるだけthisに依存しないようにしていきます
問題となるコードはこちらよりお借りします
また、typescriptで書いているのは私の趣味ですが型の明示以外にtypescriptらしいことはしていないので、
型とprivateを無視してくれればほぼjavascriptとして読めます

1: ほぼまんまのコード

export default class Sample1 {
  private some: number

  constructor() {
    this.some = 10
  }

  Start() {
    Promise.resolve()
      .then(this.TaskA)
      .then(this.TaskB)
      .then(this.TaskC)
      .catch((e) => console.log(e))
  }

  TaskA() {
    return new Promise((resolve, reject) => {
      resolve(Math.random())
    })
  }

  TaskB(value: number) {
    return new Promise((resolve, reject) => {
      resolve(this.some * value)
    })
  }

  TaskC(value: number) {
    return new Promise((resolve, reject) => {
      console.log(value)
      resolve()
    })
  }
}

const s = new Sample1()
s.Start()

問題点

TaskBがthisに依存していて、Startでthisを意識せずに書いたがゆえに壊れてしまっている
元記事で解決策も出てはいるが、Start実装者が各Taskの内部を知っている必要がある点は問題だと思った

2: メソッドを減らす

TaskAやTaskCは単純な関数だ。
基本的にどんな叩き方をしようと、TaskAはランダムな値を返し、TaskCはconsole.logで値を出力する
ただ、これらのメソッドを単独でインスタンスの外から叩くことも無いように思う
よって、ただの関数にしてしまう
どうしても外部で使いたければむしろ関数をそのままexportしたほうがキレイだろう

const taskA = (): Promise<number> => {
  return new Promise((resolve, reject) => {
    resolve(Math.random())
  })
}

const taskC = (value: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    console.log(value)
    resolve()
  })
}

export default class Sample2 {
  private some: number

  constructor() {
    this.some = 10
  }

  Start() {
    Promise.resolve()
      .then(taskA)
      .then(this.TaskB)
      .then(taskC)
      .catch((e) => console.log(e))
  }

  TaskB(value: number) {
    return new Promise((resolve, reject) => {
      resolve(this.some * value)
    })
  }
}

const s = new Sample2()
s.Start()

問題点

まだ何も解決していない
TaskBはthisに依存するし、thisをあわせていないから正しく動かない

3: this に依存するタスクをやめる

TaskBの仕様ついて考える
TaskBは引数を受け取ってthis.someと引数を乗算してresolveする非同期処理だ
非同期処理にthisが絡むと嬉しくない
TaskBを含む処理の実行中にthis.someを変更したらどうなるかわかったもんじゃないし
TaskBを利用する側がTaskBの中身を知っていなければ使いこなせないのも困る
だから、TaskBの仕様をちょっと変更する

TaskBは引数value1とvalue2を受け取り、value1とvalue2を乗算してresolveする
この仕様ならばTaskBはメソッドでなくて良いのでTaskA,Cと同じように関数にする

const taskA = (): Promise<number> => {
  return new Promise((resolve, reject) => {
    resolve(Math.random())
  })
}

const taskB = (value1: number, value2: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    resolve(value1 * value2)
  })
}

const taskC = (value: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    console.log(value)
    resolve()
  })
}

export default class Sample3 {
  private some: number

  constructor() {
    this.some = 10
  }

  Start() {
    Promise.resolve()
      .then(taskA)
      .then((resultA) => taskB(this.some, resultA))
      // アロー関数でtaskBのthisが変わったがそもそもtaskBはthisに依存していないことが重要
      .then(taskC)
      .catch((e) => console.log(e))
  }
}

const s = new Sample3()
s.Start()

Startの実装でtaskの内部を知る必要はなくなった。
各taskがthisに依存しないので、冒頭で上げた問題は解決できた。
Startメソッド実装者は自身の書いた部分のthisに気をつければ良い。

ここからさらにasync/await版やStart自体を関数にしてthis.someに依存しない用にもしてみたが、目的とずれたのでやめ。

3
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?