0から始めるPromise(javascript)

5 min読了の目安(約4700字TECH技術記事

Promiseとかthenが何をやっているのか全くわからない人向けの記事。
イラストを使って説明しているのでこれを読めば「こういう事やっているのか!」と理解できるレベルになると思います。
本題に入る前に同期処理と非同期処理の違いについて説明します。

同期処理ついて

超ざっくり説明すると上から順番に処理してくれるのが同期処理
例えば来店から完食までをconsole.logで表す同期処理は下記になる

同期処理の例

console.log("来店");
console.log("ラーメン注文");
console.log("待機中");
console.log("ラーメンお待ち!");
console.log("食事中");
console.log("御馳走様でした");

非同期処理ついて

例えばタイムラグを発生して来店から完食までをconsole.log表したいとする。
今回の例は下記のように表示させたいとする。

・画面読み込みから3秒後に「来店」
・上記の2秒後に「ラーメン注文」
・上記の1秒後に「待機中」
・上記の6秒後に「ラーメンお待ち!」
・上記の1秒後に「食事中」
・上記の5秒後に「ご馳走様でした!」

function action(action, time){
    setTimeout(() => {
      console.log(action);
    },time);
}

action("来店", 3000);
action("ラーメン注文", 2000);
action("待機中", 1000);
action("ラーメンお待ち!", 6000);
action("食事中", 1000);
action("御馳走様でした", 5000);

順番バラバラやん・・・
スクリーンショット 2020-11-08 15.15.54(3).png

なぜバラバラ表示に?

なぜこんな事が起こるかというとsetTimeout関数が非同期処理、つまり前の処理の完了を待たずに、次の処理を実行してしまうのだ。
スクリーンショット 2020-11-08 15.28.38(2).png

どうすればいいの?

最初のsetTimeout()関数の中に次に処理したい関数を書けば順番に処理される。
ただ、これだと無駄に長すぎる・・・これが俗にいうコールバック地獄です。

action("来店", 3000);

function action(come, time){
    setTimeout(() => {
      console.log(come); // 来店
      action2("ラーメン注文", 2000);
    },time);
  function action2(order, time){
    setTimeout(() => {
      console.log(order); // ラーメン注文
      action3("待機中", 1000);
    },time);
  }
  function action3(wait, time){
    setTimeout(() => {
      console.log(wait);// 待機中
      action4("ラーメンお待ち!", 5000);
    },time);
  }
  function action4(offer, time){
    setTimeout(() => {
      console.log(offer);// ラーメンお待ち!
      action5("食事中", 1000);
    },time);
  }
  function action5(meal, time){
    setTimeout(() => {
      console.log(meal); // 食事中
      action6("御馳走様でした", 4000);
    },time);
  }
  function action6(treat, time){
    setTimeout(() => {
      console.log(treat);// 御馳走様でした!
    },time);
  }
}

ここで登場するのがPromise

Promiseは非同期処理を操作できる。〇〇関数の処理が終わったら△△関数実行してね!という感じ。
実行した関数に成功した場合の処理と失敗した時の処理を書くことができます。
Promiseをnewでインスタンス(設計図)を作ります。
作ったインスタンス(設計図)はそのままreturnさせて「成功(失敗)したかどうか」を関数に返します。
返した関数はthenの中で
(
第一引数:成功した時の処理,
第二引数:失敗した時の処理
)

で実行されます。
言葉だけじゃ難しいと思うので実際に描いてみましょう。

function check(age){
  return new Promise((resolve,reject) => {
    if(age >= 20){
      resolve('成年')
    } else {
      reject('未成年');
    }
  })
}

check(21).then(
  yes => {
    console.log(yes+'。大人だね!');
  },
  no =>{
    console.log(no+'。学生かな?');
  }
)

成功(resolve)した場合のサンプル

スクリーンショット 2020-11-08 15.52.20(2).png

スクリーンショット 2020-11-08 16.25.17(3).png

失敗(reject)した場合のサンプル

スクリーンショット 2020-11-08 15.52.40(2).png

スクリーンショット 2020-11-08 16.26.20(3).png

エラー発生のサンプル(catch)

下記のコードは80点未満の場合はエラーを出す処理。
エラーの場合はcatchが読まれ、thenは処理されない。

function check(age){
  return new Promise((resolve,reject) => {
    if(age >= 80){
      resolve(age)
    } else {
      reject(new Error('エラーが発生しました'));
    }
  })
}

check(75).then(
  success => {
    console.log('あなたの点数は'+success+'点です');
  }).catch(
    error => {
    console.log(error);
  }
)

スクリーンショット 2020-11-08 16.06.07(3).png

実演

先程のラーメン店のconsole.logをスマートに書けないのか?その答えは下記の通りです。
最初のaction("来店", 3000)処理が終わらない限り、then以降の処理が実行される事はありません。

function action(action, time){
  return new Promise(resolve => {
    setTimeout(() => {
        console.log(action);
        resolve();
    }, time)
  })
}

action("来店", 3000)
.then(() => action("ラーメン注文", 2000))
.then(() => action("食事中", 1000))
.then(() => action("ラーメンお待ち!", 4000))
.then(() => action("食事中", 1000))
.then(() => action("ご馳走様でした", 5000))

スクリーンショット 2020-11-08 16.13.05(2).png

スクリーンショット 2020-11-08 16.26.20(3).png

async/awaitについて

こちらをご覧ください(後日公開予定)