非同期処理と Promise

JavaScript 一度に一つのことを処理するシングルスレッドという方式なので、基本的に書かれた順に上から実行していきます。しかし一見すると、書かれた順番には実行されていないようにみえる処理があります。それが非同期処理です。

例えば次のコードは、setTimeout を使っているので、console.log は 1000ms 後に実行されます。つまり、ある時間経過したら、何かを実行してね、とあらかじめお願いしておくような処理があるものを、非同期処理といいます。

setTimeout(() => {
  console.log("done", getSeconds());
}, 1000);

こちらもよく見るコードですが、ユーザーのクリックを待ち受けています。 このコードが実行された時には、当然ですがアラートは出ません。ユーザーがクリックするまで待って、された際にはアラートを出してね、という「予約を実行」しているわけです。

import $ from "jquery";

$(".button").on("click", () => {
  alert("click");
});

先ほど書いたコードも非同期処理です。なぜなら「データを取得するのを待って」、 取得できたら HTML を書き換えてね、という予約をしているからです。

const url = "https://developer.mozilla.org/en-US/docs/Web/HTML";

axios.get(url).then(res => {
  console.log(res.data);
  document.body.innerHTML = res.data;
});

ある処理が終わった際に実行される関数のことを「コールバック」といいますが(例えば setTimeout の例では、1000ms 後に実行される console.log() の部分です)、「コールバック」とは「折り返し電話」のことです。我々が非同期処理の際におこなっているのは、このコールバックと同じイメージで、「席が空いたら、連絡をしてね」という予約のお願いをしているわけです。

コールバック地獄

さて、予約のお願いが、一個だけなら話はシンプルですが、予約が連鎖してくると、話は複雑に、そしてコードも複雑になっていきます。

八時になったらピザを頼んで、ピザが来たら田中さんに電話をしてね。そして田中さんがこれるようなら、コーラも改めて注文しておいて。

こんなふうに予約が連鎖してくると、コードはこうなります。

setTimeout(() => {
  console.log("done1");
  setTimeout(() => {
    console.log("done2");
    setTimeout(() => {
      console.log("done3");
      setTimeout(() => {
        console.log("done4");
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

コールバックの中で、さらにコールバックを必要をするコードを追加していくと、このように入れ子が深くなってしまいます。これは書くのも、読むのも、メンテナンスするのも大変です。

さて困りました…

Promise を使ってコールバックを読みやすく書く

その解決方法の一つが「Promise」という仕組みを使うことです。

https://codesandbox.io/s/yw43kj4rv1

// 現在の時間を取得するための補助的な関数
const getSeconds = () => new Date().getSeconds();

// 非同期処理を Promise を使って読みやすく書く
const async1 = num => {
  // Promise オブジェクトを作ってこれを返す
  // Promise オブジェクトに、コールバックを登録する
  return new Promise((resolve, reject) => {
    // start
    console.log("start" + num, getSeconds());
    // delayed
    setTimeout(() => {
      console.log("done" + num, getSeconds());
      // 処理が終わる場所で、resolve を実行する
      // ここで渡した引数が、then が受け取る引数になる
      // (このコードでは result に入る)
      resolve(num);
    }, 1000);
  });
};

// promise オブジェクトを作って実行する
async1(1)
  .then(result => {
    // resolve がきたら、then に移動する
    // さらに promise オブジェクトを返すことで、
    // 次の then につなげていく
    return async1(result + 1);
  })
  .then(result => {
    return async1(result + 1);
  });

詳しくは、Promise の資料を読んで欲しいのですが、手順をまとめると以下のようになります。

  • 関数を作る。この関数は return new Promise で Promise インスタンスを作ってリターンする。

  • Promise を作る際に、実行したい非同期処理をコールバックとして登録する。

  • 非同期処理が終わる場所で resolve() を実行する。

  • 実行するには、この関数を呼び出して、Promise をインスタンス化する。

  • resolve が実行されると、次の .then() に実行が移動していく。

  • 繋げるためには、次の then の中で、また新たな Promise インスタンスを作成して、リターンしてあげればいい

Last updated