Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Timers Promises API が最高

名前から既にワクワクするこのAPIは、なんとPromiseを返すsetTimeout、setInterval関数を提供しています!最高です…

というわけで今回はそれの紹介です。

基本的な使い方

await setTimeout(1000) ←これができるんです!素晴らしくないですか??

top-level await や for-awaitと組み合わせるとこんな感じで書けます

import { setTimeout } from 'timers/promises';

console.log('start');
await setTimeout(1000); // これでいける!!
console.log('1s passed');
import { setInterval } from 'timers/promises';

console.log('start');
for await (const startAt of setInterval(1000, Date.now()) {
  console.log(Date.now() - startAt);
}

特に setTimeout は最高ですね…

キャンセルする

キャンセルはAbortControllerを使ってできます。

import { setTimeout } from 'timers/promises'

const controller = new AbortController();

(async() => {
  console.log('start');
  await setTimeout(1000, null, { signal: controller.signal });
  console.log('end');
})().catch(()=>console.log('aborted'));

// 上の setTimeout が発火する前にキャンセルしてみる
await setTimeout(500);
controller.abort();

AbortController ということは React の useEffect とも相性が良いです。

useEffect(() => {
  const controller = new AbortController();
  const { signal } = controller;

  (async () => {
    const res = await fetch('http://example.com', { signal });
    await setTimeout(1000, null, { signal });
    console.log(res);
  })();

  return () => controller.abort();
});

clearTimeout を使うよりグッとシンプルになりました。

timers/promises の 型定義

型定義がまだなさそうだったので自分で書きました。おそらく合ってますが間違えている可能性はあります。

module 'timers/promises' {
  const setTimeout: <T>(
    delay?: number,
    value?: T,
    options?: { ref?: boolean; signal?: AbortSignal }
  ) => Promise<T>;
  const setImmediate: <T>(
    value?: T,
    options?: { ref?: boolean; signal?: AbortSignal }
  ) => Promise<T>;
  const setInterval: <T>(
    delay?: number,
    value?: T,
    options?: { ref?: boolean; signal?: AbortSignal }
  ) => AsyncIterable<T>;
}

debounce や throttle を実装してみる

試しに debounce や throttle を実装してみました。Promisify されていない setTimeout を使った方がわかりやすいかもしれないです。

import { setTimeout } from 'timers/promises';

let controller: null | AbortController = null;
export const debounce = <T extends unknown[]>(
  f: (...args: T) => void,
  wait: number
) => async (...args: T) => {
  if (controller) controller.abort();
  controller = new AbortController();
  setTimeout(wait, null, controller)
    .then(() => f(...args))
    .catch(() => {});
};
import { setTimeout } from 'timers/promises';

let timer: null | Promise<unknown> = null;
export const throttle = <T extends unknown[]>(
  f: (...args: T) => void,
  wait: number
) => async (...args: T) => {
  if (timer == null) {
    f(...args);
    await (timer = setTimeout(wait));
    timer = null;
  }
};