はじめに
オレオレutil関数の中で便利と思ったものを紹介していきたい。
iife
const iife = <T>(f: () => T) => f()
使い方
before
const random = Math.random()
let result: number
if (random < 0.3) {
result = 3
} else if (random < 0.6) {
result = 6
} else if (random < 0.9) {
result = 9
} else {
result = 10
}
// 以降はrandomを利用しないユースケースを想定
after
const result = iife(() => {
const random = Math.random()
if (random < 0.3) return 3
if (random < 0.6) return 6
if (random < 0.9) return 9
return 10
})
// result: 3 | 6 | 9 | 10
説明
名前はImmediately Invoked Function Expressionが由来。
(これを紹介するためにこのqiita記事を書いている。)
メリット
- ローカル変数をその返り値を計算するためだけに利用している場合、ローカル変数のスコープを小さくできる。
- この例だとrandomが他の場所で使われているかを考える必要がない。
- asyncにも対応している
- 元々欲しかったが、purescriptやhaskellのwhereを見てやはり間違ってなかったと感じた。
- lodashのapplyなどでもおそらく同じようなことができるが、意図としてiifeだと「その場で」「ただ変数のスコープを小さくしたいだけ」という意図を伝えられると思う。
- 上の例の場合、if分岐が1つまでならまだ三項演算子で対応できるがそれ以外だとiifeの方がreturnが使えて便利。三項演算子に長い処理があると読みにくいがこれなら読みやすい。
- わざわざ別関数を用意しなくても済む(逆に、たいていの場合はiifeを使わずに別関数に切り出して同じ効果を得ることもできるはず)
- 本来のiifeでも良いが、関数の最後まで見ないと実際にすぐに定義した関数が呼ばれているのか(iifeが行われているのか)別のことをしているのか分からない。
デメリット
- 使っている人を見たことがなく、初めて読む人はiifeだらけで読みづらい
getNow
const getNow = () => new Date()
使い方
before
const user = new User({
name,
gender,
createdAt: new Date(),
updatedAt: new Date(),
})
User.save(user)
after
const user = new User({
name,
gender,
createdAt: getNow(),
updatedAt: getNow(),
})
User.save(user)
説明
全てのnew DateをgetNow()に置き換えて使う。
メリット
- jestなどでmockするとき、getNow関数だけmockして、例えば2100/2/3を返すように変更すれば、システム全体がまるで2100/2/3にタイムスリップしたかのように振る舞う。
- たとえばお年玉企画のため、毎日動いているバッチ処理の中でも現在時刻が1/1,1/3,1/5それぞれだった場合には特別なバッチ処理をしたい場合などにおいて、ただしくこれらの日時になったときに動くかをgetNow関数をmockすることで簡単にテストできる。
デメリット
- new Dateしていた方が可読性が高い。
- 毎回のimportが面倒
その他
const noop = () => {} // 何もしない処理。callback引数のデフォルト引数にするなど。
const mapAsyncAll // mapの引数関数がasyncでもawaitできるなど。
const mapAsyncSeqAll // mapの引数関数がasyncでもawaitできるなど。前から順番に実行。
const forEachAsyncAll // forEachの引数関数がasyncでもawaitできるなど。
const forEachAsyncSeqAll // forEachの引数関数がasyncでもawaitできるなど。前から順番に実行。