Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@7shi

Promiseの処理をキャンセルする

Promise で Web Speech API をラップして使っていましたが、キャンセルできるように実装するのに試行錯誤しました。想定していたような動きが実現できたので、メモを残しておきます。

See the Pen Web Speech API with Promise by 七誌 (@7shi) on CodePen.

↑ エラーになる場合は一度 CodePen を開いてから、この記事をリロードしてください。

概要

前回の記事では正常終了 onendresolve、異常終了 onerrorreject として扱いました。

function speak(lang, text) {
    return new Promise((resolve, reject) => {
        let u = new SpeechSynthesisUtterance(text);
        u.lang = lang;
        u.onend = resolve;
        u.onerror = reject;
        speechSynthesis.speak(u);
    });
}

読み上げ中に speechSynthesis.cancel() を呼ぶことでキャンセルできます。通常終了と同じ onend イベントが発生するため、イベントではキャンセルされたことが検知できません。

何らかの手段でキャンセルされたことを通知する必要があります。

戻り値

resolve への引数は await を通して戻り値になります。

ブラウザのコンソール
> p = new Promise((resolve, reject) => resolve(123))
> await p
123

これを利用して正常終了かキャンセルかを戻り値で区別するように speak を実装します。例外を無視するため終了と同じ扱いとします。

let stop = () => false;
function speak(lang, text) {
  return new Promise((resolve, reject) => {
    let speakend = cancel => {
      speakend = () => false;
      if (cancel) speechSynthesis.cancel();
      resolve(cancel);
      return cancel;
    };
    stop = () => speakend(true);
    let u = new SpeechSynthesisUtterance(text);
    u.lang = lang;
    u.onend = u.onerror = () => speakend(false);
    speechSynthesis.speak(u);
  });
}

キャンセルするには外部から stop() を呼びます。Promise のコンストラクタで stop を書き換えて speakend 経由で resolve(true) を呼べるようにしておくことで、終了イベント onend よりも先に終了させます。

利用方法

複雑さは Promise の中に閉じ込めたため、利用側のコードは簡単になります。

button.onclick = async function() {
  if (stop()) return;
  button.textContent = "Stop";
  for (let [element, lang, text] of texts) {
    element.classList.add("speaking");
    let cancel = await speak(lang, text);
    element.classList.remove("speaking");
    if (cancel) break;
  }
  button.textContent = "Start";
};

ループによっていくつかのテキストを読み上げます。speaking をマークすることで読み上げ個所を示します。await speak() から戻ってマークを解除して、キャンセルされていればループから抜けます。

reject で例外によってキャンセルを通知することも可能ですが、今回は resolve によって戻り値で通知した方が利用側のコードが簡単になると判断しました。

戻り値は正常終了のときに true にした方が自然かもしれませんが、今回はキャンセルに注目して値を設定しました。

参考

Web Speech API の使い方は以下の記事を参照してください。

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
7shi

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?