17
5

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 1 year has passed since last update.

AbortControllerを使って、Promiseを中断する方法

Last updated at Posted at 2022-07-05

はいさい!ちゅらデータぬオースティンやいびーん

概要

AbortController APIを使って、Promiseを中断・強制的に拒否する方法を紹介します!

背景

非同期処理を、Promiseの外から止めたい!と、思ったことはありませんでしょうか?

社内で、Fetch API以外の非同期処理の作業を中断する方法がないか議論がありましたが、筆者はAbort Controllerを使ったらいいのではないかという考えで一つの解決法を思いつきました。

Abort ControllerはFetch APIのリクエストを中断するのに使われることは周知の通りですが、Abort Controllerは実に多くの使い方があります。

以前の記事で紹介したように、Abort ControllerでEventListenerを外すこともできます。

コード

Abort ControllerのsignalにEventListenerを追加する

Abort ControllerのAbortController.signalは、AbortController.abort()が実行された時に、"abort"というイベントを配信します。
AbortController.signalにこの"abort"イベントを受信するEventListenerを追加することが可能です。

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

signal.addEventListener("abort", () => {
  console.log("Aborted!");
});

controller.abort(); // "Aborted!"

PromiseでAbort Controllerを使って拒否する

次は、上記の手法を活かして、Promiseの外からPromiseを拒否させます。

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

const promise = new Promise((resolve, reject) => {
  let count = 10;

  const intervalId = setInterval(() => {
    count--;
    console.log("Count Down set in Executor", count);
    if (count < 1) {
      clearInterval(intervalId);
      resolve(48);
    }
  }, 1000);

  signal.addEventListener(
    "abort",
    () => {
      clearInterval(intervalId);
      reject(20);
    },
    {
      once: true,
    }
  );
});

promise.then((num) => console.log("Resolved! ", num)).catch((num) => console.log("Rejected! ", num));

setTimeout(() => controller.abort(), 3000);

本来なら、10秒以上経って、解決されるはずのPromiseが、3秒くらい経って拒否されるのです!

Promiseを拒否させて、履行状態にしたのですが、ここで注意しないといけないのは、Promiseが履行されたからといって、windowに対して付けたsetIntervalは続きます。

signal.addEventListenerでは、onceの設定を正にすることで、signalに付けたEventListenerが呼び出された時に、自動的に取り外されます。

現実的な例:File Reader API

次の例では、File Reader APIでファイルを開こうとしたが、時間がかかりすぎたので、中断したいという前提のコードです。

File Reader APIのPromise化の仕方については、以前書いた記事をご参考ください。

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

const fileReadPromise = new Promise((resolve, reject) => {
  const file = document.querySelector("input[type=file]").files[0];
  const reader = new FileReader();

  reader.onload = () => {
    resolve(reader.result);
  };

  reader.onerror = () => {
    reject(reader.error);
  };

  signal.addEventListener(
    "abort",
    () => {
      reader.abort();
      reject(new DOMException("File Read Aborted."));
    },
    { once: true }
  );

  reader.readAsDataURL(file);
});

fileReadPromise.then((dataURL) => {
  const img = document.querySelector("img#preview");
  img.setAttribute("src", dataURL);
});

controller.abort(); // File Read Aborted.

ここで、cancelCallbackで強制的にFile Readerの読み込み作業を中断させた上で、Promiseを拒否しているので、実際のファイルの読み込み作業はPromiseが履行状態になった時に、止まっているはずです。

signal.addEventListenerでは、onceの設定を正にすることで、signalに付けたEventListenerが呼び出された時に、自動的に取り外されます。

まとめ

いかがでしょうか?Abort Controllerはとても便利な技術で、何かを止めたいという時にとっておきの手段です。

ぜひ他の応用例を筆者に共有していただければと思います!

17
5
1

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
17
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?