JavaScript:使わないと忘れそうなPromiseのthenチェーンについての備忘録(自分用)

やあ子供たち。今日はJavascriptのPromiseのthenチェーンについてのおじさんなりの備忘録だよ。あのな、なんだってそうなんだけどな、勉強したことや聞いた話ってのは頭の中でとにかく抽象化、つまり「ひとことで言うとこうだ!」みたいな感じで、それだけ覚えておくようにするんだよ。そうすれば他のことは全部きれいに忘れ去っても、これは何のことはない「ひとことで言うとこう」なんだからって、全部自動的にそこから芋蔓式に思い出せるんだね。例えば二次関数の解の公式を忘れても、いつだって平方完成すれば自分で公式を導けるようにね。
さて前置きが長くなったが今日はPromiseのthenチェーンについてだよ。まずはPromiseのおさらいから行こうか。
Promiseとはずばり、「任意の処理を、関数として自身の中に定義・保持したもの。定義した処理は、そのインスタンスに対してthen()を呼び出すことで、実行される。定義した処理の完了は、そのインスタンスに対してthen()を呼び出すことで、確認される。」だよ。これだけは今日覚えて帰ってくれ。これだけでもいいよ最低限な話すると。それくらい重要だぞこれは。そしてもう少し覚えて帰ってほしいことをいけばこうだ。→Promiseとは、「then()で実行されるが、このthenに、( func_resolve, func_reject ) という二つの関数を指定でき、前者は即ち、Promise定義時に指定する関数の一つ目の引数、後者は二つ目の引数として使われる。」だ。
さて、このthen()は、その返り値として、やはりPromiseを返すので、チェーンのように繋げることができるという。今日はね、このthenチェーンについてなんだけど、ひとことで覚えるとしたらね、こうだよ。「thenチェーンは、処理を同期的につなぐこともできるし、途中に非同期な処理をいれてつなぐこともできる」かな。つまり複数の処理を順番に行わせたいんだけど、この処理はすぐ終わるので同期的に次の処理(then呼び出し)にいかせたい。ということもできるし、途中のこの処理は時間かかるので非同期に実行させて、それが終わったら次(のthen呼び出し)、みたいなふうに、一連の複数の処理を、同期的だろうが、非同期的(「終わったら次みたいな」)だろうと、とにかく、指定した順番どおりに、前の処理が確実に終わってから次の処理、というやり方で実行させることができるというものだ。この、同期的につなぎたい、すぐ終わる処理を書きたい場合は、thenに指定する関数の中で単に「値」をreturnします。
以下の例を見てくれ。
thenチェーン、ケース①(同期処理を順番に実行させたい場合)

function promise_test_1()
{
  let ui = document.getElementById("ui");
  let prom1 = new Promise(function (resolv, reject) {
    var arr = [];
    arr.push(1);
    ui.textContent += "[" + arr + "]";
    setTimeout(() => { resolv(arr); }, 1000);
  });
  prom1.then(function (arr) {
    arr.push(2);
    ui.textContent += "[" + arr + "]";
    return arr;
  }).then(function (arr) {
    arr.push(3);
    ui.textContent += "[" + arr + "]";
    return arr;
  }).then(function (arr) {
    arr.push(4);
    ui.textContent += "[" + arr + "]";
    return;
  });
}

「then()はPromiseそのものを返すというわけなのに、このthenの中では値を返しているな。変な話だな?」なんて思ってはいけないよ。よく見てくれ。then()の返り値ではないよね。then()に指定している関数の返り値として値を返しているだけだろ、な、そうだろ。この結果はこうなるよ。以下の結果[1],[1,2][1,2,3],[1,2,3,4]が、いちどきに表示されるぞ。

●thenチェーン、ケース②(非同期処理なんだけどあくまで順番に実行させたい場合)
さて次は以下のコードだ。上述したように「いやすぐには終わらない、非同期に実行させたい処理を順番にやらせたいんだよ」という場合は、こういうふうに書いたりするわけだ。

function promise_test_2()
{
  let ui = document.getElementById("ui2");
    let prom1 = new Promise(function (resolv, reject) {
    var arr2 = [];
    arr2.push(1);
    ui.textContent += "[" + arr2 + "]";
    setTimeout(() => { resolv(arr2); }, 1000);
  });
  prom1.then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(2);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv(arr2); }, 1000);
    });
  }).then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(3);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv(arr2); }, 1000);
    });
  }).then(function (arr2) {
    arr2.push(4);
    ui.textContent += "[" + arr2 + "]";
    return;
  });
}

はい、このように、thenに指定する関数の中で、明示的に非同期処理を定義したPromiseを作成してそれを、returnしてあげるんですよ。
この場合、ブラウザ出力は以下のようになるよ。この場合は、いちどきに表示されるんじゃなくて、「[1]」が表示されてから1秒後に、「1,2」が表示され、またその1秒後に、「1,2,3」が表示され、またその1秒後に、「1,2,3,4」が表示されるぞ。

●thenチェーン、ケース③(②の確認)
じゃ最後、以下のコードではどうか。

function promise_test_3()
{
  let ui = document.getElementById("ui3");
  let prom1 = new Promise(function (resolv, reject) {
    var arr2 = [];
    arr2.push(1);
    ui.textContent += "[" + arr2 + "]";
    setTimeout(() => { resolv(arr2); }, 1000);
  });
  prom1.then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(2);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv(arr2); }, 1000);
    });
  }).then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(3);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv([7, 7, 7, 7]); }, 1000);
    });
  }).then(function (arr2) {
    arr2.push(4);
    ui.textContent += "[" + arr2 + "]";
    return;
  });
}

ちゃんとresolv()に引数として指定した値が次のthenに指定した関数の引数としてわたっているのがわかるだろ?そこが腑に落ちるように用意したサンプルなだけだよこの3つ目のサンプルはさ。もちろんこの場合も、[1]」が表示されてから1秒後に、「1,2」が表示され、またその1秒後に、「1,2,3」が表示され、またその1秒後に、file「7,7,7,7,4」が表示されるぞ。

最後に全ソースは以下のようだよ。

<html lang="ja">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<head>
<style type="text/css">
#ui{ color: #a00; }
#ui2{ color: #0a0; }
#ui3{ color: #00a; }
</style>
</head>
<script type="text/javascript">

function promise_test_1()
{
  let ui = document.getElementById("ui");
  let prom1 = new Promise(function (resolv, reject) {
    var arr = [];
    arr.push(1);
    ui.textContent += "[" + arr + "]";
    setTimeout(() => { resolv(arr); }, 1000);
  });
  prom1.then(function (arr) {
    arr.push(2);
    ui.textContent += "[" + arr + "]";
    return arr;
  }).then(function (arr) {
    arr.push(3);
    ui.textContent += "[" + arr + "]";
    return arr;
  }).then(function (arr) {
    arr.push(4);
    ui.textContent += "[" + arr + "]";
    return;
  });
}

function promise_test_2()
{
  let ui = document.getElementById("ui2");
    let prom1 = new Promise(function (resolv, reject) {
    var arr2 = [];
    arr2.push(1);
    ui.textContent += "[" + arr2 + "]";
    setTimeout(() => { resolv(arr2); }, 1000);
  });
  prom1.then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(2);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv(arr2); }, 1000);
    });
  }).then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(3);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv(arr2); }, 1000);
    });
  }).then(function (arr2) {
    arr2.push(4);
    ui.textContent += "[" + arr2 + "]";
    return;
  });
}

function promise_test_3()
{
  let ui = document.getElementById("ui3");
  let prom1 = new Promise(function (resolv, reject) {
    var arr2 = [];
    arr2.push(1);
    ui.textContent += "[" + arr2 + "]";
    setTimeout(() => { resolv(arr2); }, 1000);
  });
  prom1.then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(2);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv(arr2); }, 1000);
    });
  }).then(function (arr2) {
    return new Promise(function (resolv, reject) {
      arr2.push(3);
      ui.textContent += "[" + arr2 + "]";
      setTimeout(() => { resolv([7, 7, 7, 7]); }, 1000);
    });
  }).then(function (arr2) {
    arr2.push(4);
    ui.textContent += "[" + arr2 + "]";
    return;
  });
}

window.addEventListener("DOMContentLoaded",function(){

  promise_test_1();
  promise_test_2();
  promise_test_3();  

});
</script>
<body>
<h1 id="ui"> hello  </h1>
<h4>↑値をreturnした場合のthenチェーン。複数の処理を同期的に順番に処理。<br>return した値がそのまま次のthenに指定した関数の、引数となる。</h4>
<h1 id="ui2"> hello </h1>
<h4>↑明示的に作成したPromiseをreturnした場合のthenチェーン。複数の処理を非同期的に順番に処理。<br>return したPromiseの中でresolvの引数に指定した値がそのまま次のthenに指定した関数の、引数となる。</h4>
<h1 id="ui3"> hello </h1>
<h4>↑明示的に作成したPromiseをreturnした場合のthenチェーン。複数の処理を非同期的に順番に処理。<br>return したPromiseの中でresolvの引数に指定した値がそのまま次のthenに指定した関数の、引数となることを確かめてみたもの。</h4>
</body>
</html>

これをhello_promise.htmlファイルとして保存して、Chromeとかで実行してみてくれよな。
じゃ今日はこの辺で。チャオ!