FetchAPIで非同期通信をやってみる

非同期アイキャッチ

新規会員登録機能というテイで、メールアドレス重複チェックをページ遷移無しで行うようにします。
このご時世なのでこんな機能は不要かと思われますが、勉強のためですので大目に見てください。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .red {
      color: #ff0000;
    }
  </style>
  <script src="main.js"></script>
</head>
<body>

  <form name="formbbs" id="formbbs">
    なまえ<input type="text" name="your_name"><br>
    メールアドレス<input type="email" name="email" id="email"><br>
    <input type="submit" id="submit" value="送信">
  </form>

</body>
</html>
'use strict';

document.addEventListener('DOMContentLoaded', main);


function main() {
  const email = document.getElementById('email');
  email.addEventListener('keyup', () => {

    let val = email.value;
    console.log(val);

    if(val.match(/^[A-Za-z0-9]+[\w-]+@[\w\.-]+\.\w{2,}$/)) {
      const url = 'db.php';
      const post_options = {
        method: 'post',
        body: JSON.stringify({
          "email": val,
        }),
      };
      fetch(url, post_options)
        .then( response => {
          if(response.ok) {
            return response.json();
          }
            throw new Error('エラーです。');
        }).then( json => {
          console.log(json);
          if(json.errorFlg) {
            const newP = document.createElement('span');
            newP.id = 'warning';
            newP.classList.add('red');
            newP.textContent = json.msg;
            const formbbs = document.getElementById('formbbs');
            formbbs.insertBefore(newP, email.nextSibling);
          } else {
            const warning = document.getElementById('warning');
            if(warning) {
              warning.parentNode.removeChild(warning);
            }
          }
        }).catch( e => console.log(e.message));
     }
  });
}
<?php
  require 'function.php';

  $input = json_decode(file_get_contents("php://input"), true);

  if(!empty($input)) {
    $dbh = dbConnect();
    $sql = 'SELECT * FROM users WHERE email = ?';
    $row = dbExec($dbh, $sql, $input['email']);
    if($row > 0) {
      echo json_encode([
        'errorFlg' => true,
        'msg' => '既に登録されています',
      ]);
    } else {
      echo json_encode([
        'errorFlg' => false,
        'msg' => '',
      ]);
    }

    exit();
  }
<?php
  function dbConnect() {
    $dsn = 'mysql:dbname=test_bbs;host=localhost;charset=utf8';
    $user = 'root';
    $pass = 'root';
    $options = [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
      PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
    ];

    try {
      return new PDO($dsn, $user, $pass, $options);
    } catch(PDOException $exception) {
      print_r($exception->getMessage(), true);
      die();
    }
  }


  function dbExec($dbh, $sql,...$params) {
    try {
      if($dbh === null) {
        return false;
      }
      if($params !== null && count($params)) {
        $stmt = $dbh->prepare($sql);

        foreach($params as $index => $param){
          $stmt->bindValue($index+1, $param);
        }

        $stmt->execute();
        return $stmt->rowCount();
      }
      return $dbh->exec($sql);


    } catch(PDOException $exception) {
      print_r($exception->getMessage(), true);
      die();
    }
  }

実際の挙動が以下でございます。

データベースの中身はこちらです。

aaa@example.coまで打つと、ajax通信を行なっています。
その後、aaa@example.comまで打つと結果が返ってきています。

PHPの部分は割愛して、JavaScriptの部分を解説します。

  1. DOMContentLoadedで、DOMの読み込みが終わったらmain関数を呼び出します。
  2. email入力欄に入力があったらイベントを発火。
  3. まず、その入力された値がemailの形式かどうかチェックし、形式があっていれば次へ進みます。
  4. fetchの引数は2つ。
    第1引数にurl第2引数にオプションを指定します。
    第2引数内もjson文字列で記述。
    methodをPOST、bodyに送りたい内容をjson文字列に変換して送ります。
    https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch
  5. fetchはPromiseを返すので、.thenで繋げて、responseを受け取ります。
    しかし、このままだとresponseのbodyがReadableStreamとなっていて使えません。
    さらに.thenを繋いで、response.json()で受け取ります。

    ボディのテキストを JSON として解釈した結果で解決する promise を返します。(https://developer.mozilla.org/ja/docs/Web/API/Body/json
  6. その前に、、、promiseは状態と結果を持っていると上のほうで説明していました。
    fetch() promiseはサーバーと通信して、Responseが返ってきたらOKという状態を持ってしまいます。

    fetch() が成功したかどうかの明確な判定をするには、プロミスが解決されて、Response.ok プロパティが true になっているかを確認します。(https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch

    そのため、if文でresponse.okという条件をつけて、trueなら処理、falseならエラーをthrowして、最後の.catch()で取ってあげています。
  7. 最後に、jsonとして受け取ったものをHTMLに書き出します。