Skip to content

Instantly share code, notes, and snippets.

@ttsuki
Created May 9, 2023 15:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ttsuki/ae2361a6d2f0fef60006ef05f5884b4f to your computer and use it in GitHub Desktop.
Save ttsuki/ae2361a6d2f0fef60006ef05f5884b4f to your computer and use it in GitHub Desktop.
今まで、fetchとかthenとか、他のコードみて雰囲気で使ってたけど、つまるところ、何がどうなってるのか、ざっくりわかっておきたくなったので、今更ながらちょっと勉強した、その備忘メモ。

今まで、fetchとかthenとか、他のコードみて雰囲気で使ってたけど、つまるところ、何がどうなってるのか、ざっくりわかっておきたくなったので、今更ながらちょっと勉強した、その備忘メモ。

JavaScript 非同期処理

JavaScriptはシングルスレッドだけど非同期実行の仕組みがある。 古くは setTimeout だけど、今はPromiseThenableなる概念があって、 fetch() とかがこれで実装されている。

PromiseThenable

MDNでは、ある非同期処理Promiseは「待機 (pending) 」「履行 (fulfilled)」「拒否(rejected)」のいずれかの状態を持つと説明される。

意味がとりにくいので、ここではそれぞれを「未完了」「完了:成功」「完了:失敗」とよびかえることにする。

「完了:成功」「完了:失敗」の状態においては、付随して値を持てる。成功時は処理結果を表す実際の値(計算結果など)、失敗時は失敗理由(例外オブジェクトなど)を持たせるのが一般的な使い方と思われる。

C++だと、Promisestd::promise<T>Thenablestd::future<T>、 C# だと、 PromiseTask<T>ThenableIAsyncResult<T> に対応する感じ。

=====

開始と待機

非同期処理を開始する関数は、その結果を受け取る thenable を返す。

非同期処理を待つ側は、戻り値の Thenable.then() method に callback を登録しておくと、完了したときに呼んでくれるようになる。

then() を呼んだ時点では、処理が予約されるだけであって、そこで制御が待つわけではない。これは、 setTimeout の呼び出しに似ている。そこで待つかのように書きたいときは await を使う。(ただし、awaitする関数はasyncである必要がある。後述)

使用例

const thenable = new Promise(
  (resolve, reject) => {
    setTimeout(() => { 
      if (confirm("Success?"))    
        resolve('Success!'); // promise を 成功状態にする。値は 'Success!'
      else
        reject(new Error("Failed!")); // promise を失敗状態にする。値は Error
    }, 1000);
  });

thenable
  .then(
    (value) => { console.log("SUCCESS: ", value); return value; }, // value を伝播
    (reason) => { console.log(" FAILED: ", reason); throw reason; } // reason を伝播
  )
  .catch(
    (reason) => { console.log(" CATCH: ", reason); throw reason; } // reason を伝播
  )
  .then(
    (value) => { console.log("SUCCESS: ", value); return value; }, // value を伝播
    (reason) => { console.log(" FAILED: ", reason); return 42; } // error を握りつぶす
  )
  .then(
    (value) => { console.log("SUCCESS: ", value); return value; }, // 
    (reason) => { console.log(" FAILED: ", reason); throw reason; } // 呼ばれない
  )
  .finally(
    () => { console.log(" FINALLY. "); }
  )

Thenable インターフェースの使い方

Thenable なインターフェースを持つオブジェクトは then() method を持つ。

then() method の基本的な使い方は、「引数を1つとるコールバック関数」を最大2つとる。

その Thenable が指す非同期処理が、

  • 「成功」状態になったら1番目のコールバックを呼び出す
  • 「失敗」状態になったら2番目のコールバックを呼び出す

then() method は、then() メソッドに渡された関数が……

  • Thenable を返した場合、それを返す。
  • Thenable でないものを返した場合、暗黙的に、それを「完了:成功」状態の Promise で包んで Thenable として返す。
  • 例外を送出した場合、暗黙的に、それを「完了:失敗」状態のPromiseで包んで Thenable として返す。

この挙動によって、 then(...).then(...).then(...) とメソッドチェーンをつなぐことができる。 非同期実行の必要のないコールバックを途中に挟みたいとき、手づからPromiseで包む必要がないのも便利。

then の仲間

.catch( (reason)=>... )

成功した場合は値を素通りさせ、thenの第2引数だけを渡したい場合に使う。すなわち

  .then((t) => t, (reason)=>...)

を簡単かつわかりやすく書いたものと思えばいいかしら。

  • エラーを適切に処理し、あるいは握りつぶしたければ、何らか値を返せば後続する then は、その値で「成功」 fullfilled 続行。
  • ログ出しなどしてそのままエラー状態を維持したいときとかは、再throwすれば、後続のthenはその値で「失敗」 rejected 続行。

.finally( ()=>... )

成功してても失敗しててもどっちの場合でも何かしたいとき使う。 finallyに渡したコールバックハンドラが何かをthrowすると、後続はその値で rejected。それ以外は context な promise そのまま、(成功/失敗の状態変更なしに)続行。

Promise オブジェクトの使い方

Promiseコンストラクタに非同期実行したいコールバックを渡す。

このコールバックの引数として、2つの関数 (resolve, reject) が渡される。

const thenable = new Promise(
  (resolve, reject) => {
    if (confirm("Success?"))    
      resolve('Success!'); // promise を 成功状態にする。値は 'Success!'
    else
      reject(new Error("Failed!")); // promise を失敗状態にする。値は Error
  });

コールバックで処理がおわったら

  • resolve を呼んで、そのPromiseを「完了:成功」状態にするか、
  • reject を呼ぶか何かを throw して、「完了:失敗」状態にする ように実装すると、then が発火する。

複数の Promise をまとめて扱う

  • Prmise.all( [p0, p1, p2, ... ] ): 全部成功
    • 全部が「成功」したら成功。成功時の値は配列で得られる。 [ p0の結果, p1の結果, p2の結果, ...]
    • いずれかが「失敗」したら失敗。失敗時の値は最初に報告された失敗の値
  • Promise.any( [p0, p1, p2, ... ] ): どれか成功
    • いずれかが「成功」したら成功。成功時の値は最初に報告された成功の値
    • 全部が「失敗」したら失敗。失敗時の値は AggregateError にまとめられる。
  • Promise.race( [p0, p1, p2, ... ] ): 最初の完了
    • いずれかが「成功」したら成功。成功時の値は最初に報告された成功の値
    • いずれかが「失敗」したら失敗。失敗時の値は最初に報告された失敗の値
  • Promise.allSettled( [p0, p1, p2, ... ] ): 全部完了
    • 常に成功。成功時の値はそれぞれの完了値。
      • status, によって valuereason かどっちかを持ったオブジェクト。こんな。
      [ { status: "fulfilled", value: 3 }
      , { status: "rejected", reason: "foo" }
      ]
    • 失敗しない。

async functionawait

async function

関数宣言のときに async キーワードを付けると、その関数本体を Promise で囲んでくれる。その関数の呼び出しを、thenableを返すので、.then() とかで待てるようになる。

await

await式は、async function の中でだけ使える。

  await `Promise-Expression`

thenable を待ちながら、呼び出し元に制御を返す。

awaitthenable でないものに await をつけても合法。その場合は直ちに履行される。

関連: async function*for await()

こっちは、function*async版。ジェネレータとそれを受けるforループを非同期にできる。っていうか、JavaScriptにジェネレータ構文あったの知らなかった……

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment