JavaScript Promise

概要:このチュートリアルでは、JavaScriptのPromiseについて学び、それらを効果的に使用する方法を学びます。

JavaScript Promiseがなぜ必要なのか

次の例では、ユーザーのオブジェクトのリストを返す関数getUsers()定義します。

function getUsers() {
  return [
    { username: 'john', email: '[email protected]' },
    { username: 'jane', email: '[email protected]' },
  ];
}Code language: JavaScript (javascript)

各ユーザーオブジェクトには、usernameemailの2つのプロパティがあります。

getUsers()関数によって返されるユーザーリストから、ユーザー名でユーザーを検索するには、次のようにfindUser()関数を使用できます。

function findUser(username) {
  const users = getUsers();
  const user = users.find((user) => user.username === username);
  return user;
}Code language: JavaScript (javascript)

findUser()関数では

  • まず、getUsers()関数を呼び出してユーザー配列を取得します。
  • 次に、Arrayオブジェクトのfind()メソッドを使用して、特定のusernameを持つユーザーを検索します。
  • 3番目に、一致したユーザーを返します。

以下は、ユーザー名'john'を持つユーザーを検索するための完全なコードを示しています。

function getUsers() {
  return [
    { username: 'john', email: '[email protected]' },
    { username: 'jane', email: '[email protected]' },
  ];
}

function findUser(username) {
  const users = getUsers(); 
  const user = users.find((user) => user.username === username);
  return user;
}

console.log(findUser('john'));
Code language: JavaScript (javascript)

出力

{ username: 'john', email: '[email protected]' }Code language: CSS (css)

findUser()関数のコードは、同期的でブロッキングです。findUser()関数は、getUsers()関数を実行してユーザー配列を取得し、users配列に対してfind()メソッドを呼び出して特定のユーザー名のユーザーを検索し、一致したユーザーを返します。

実際には、getUsers()関数はデータベースにアクセスしたり、APIを呼び出してユーザーリストを取得したりする場合があります。したがって、getUsers()関数には遅延が発生します。

遅延をシミュレートするには、setTimeout()関数を使用できます。例:

function getUsers() {
  let users = [];

  // delay 1 second (1000ms)
  setTimeout(() => {
    users = [
      { username: 'john', email: '[email protected]' },
      { username: 'jane', email: '[email protected]' },
    ];
  }, 1000);

  return users;
}Code language: JavaScript (javascript)

仕組み。

  • まず、配列usersを定義し、その値を空の配列で初期化します。
  • 次に、setTimeout()関数のコールバック内で、ユーザーの配列をusers変数に割り当てます。
  • 3番目に、users配列を返します。

getUsers()は正しく機能せず、常に空の配列を返します。したがって、findUser()関数は期待どおりに機能しません。

function getUsers() {
  let users = [];
  setTimeout(() => {
    users = [
      { username: 'john', email: '[email protected]' },
      { username: 'jane', email: '[email protected]' },
    ];
  }, 1000);
  return users;
}

function findUser(username) {
  const users = getUsers(); // A
  const user = users.find((user) => user.username === username); // B
  return user;
}

console.log(findUser('john'));
Code language: JavaScript (javascript)

出力

undefinedCode language: JavaScript (javascript)

getUsers()が空の配列を返すため、users配列は空です(A行)。users配列でfind()メソッドを呼び出すと、メソッドはundefinedを返します(B行)。

課題は、1秒後にgetUsers()関数から返されたusersにアクセスする方法です。古典的なアプローチの1つは、コールバックを使用することです。

非同期操作に対処するためのコールバックの使用

次の例では、コールバック引数をgetUsers()関数とfindUser()関数に追加します。

function getUsers(callback) {
  setTimeout(() => {
    callback([
      { username: 'john', email: '[email protected]' },
      { username: 'jane', email: '[email protected]' },
    ]);
  }, 1000);
}

function findUser(username, callback) {
  getUsers((users) => {
    const user = users.find((user) => user.username === username);
    callback(user);
  });
}

findUser('john', console.log);Code language: JavaScript (javascript)

出力

{ username: 'john', email: '[email protected]' }Code language: CSS (css)

この例では、getUsers()関数は引数としてコールバック関数を受け取り、setTimeout()関数内でusers配列を使用してそれを呼び出します。また、findUser()関数は、一致したユーザーを処理するコールバック関数を受け入れます。

コールバックアプローチは非常にうまく機能します。ただし、コードがより追跡しにくくなります。また、コールバック引数を持つ関数に複雑さが追加されます。

関数の数が増えると、コールバック地獄の問題が発生する可能性があります。これを解決するために、JavaScriptはPromiseの概念を導入しました。

JavaScript Promiseの理解

定義上、Promiseは、非同期操作の結果をカプセル化するオブジェクトです。

Promiseオブジェクトには、次のいずれかの状態を持つことができます。

  • 保留中
  • で満たされた
  • 理由で拒否された

最初は、Promiseの状態は保留中で、非同期操作が進行中であることを示します。非同期操作の結果に応じて、状態は満たされた状態または拒否された状態のいずれかに変化します。

満たされた状態は、非同期操作が正常に完了したことを示します。

JavaScript Promise Fulfilled

拒否された状態は、非同期操作が失敗したことを示します。

Promiseの作成

Promiseオブジェクトを作成するには、Promise()コンストラクターを使用します。

const promise = new Promise((resolve, reject) => {
  // contain an operation
  // ...

  // return the state
  if (success) {
    resolve(value);
  } else {
    reject(error);
  }
});Code language: JavaScript (javascript)

Promiseコンストラクターは、通常は非同期操作を実行するコールバック関数を受け入れます。この関数は、多くの場合、実行者と呼ばれます。

次に、実行者はresolverejectという名前の2つのコールバック関数を受け入れます。

実行者に渡されるコールバック関数は、慣例としてのみresolveおよびrejectであることに注意してください。

非同期操作が正常に完了した場合、実行者はresolve()関数を呼び出して、Promiseの状態を保留中から、値で満たされた状態に変更します。

エラーが発生した場合、実行者はreject()関数を呼び出して、Promiseの状態を保留中からエラー理由で拒否された状態に変更します。

Promiseが満たされた状態または拒否された状態に達すると、その状態にとどまり、別の状態に移行することはできません。

言い換えれば、Promiseは、満たされた状態から拒否された状態に移行することはできません。また、その逆も同様です。また、満たされた状態または拒否された状態から保留中の状態に戻ることもできません。

新しいPromiseオブジェクトが作成されると、その状態は保留中です。Promiseが満たされたまたは拒否された状態に達すると、それは解決済みになります。

実際には、Promiseオブジェクトを作成することはほとんどないことに注意してください。代わりに、ライブラリによって提供されるPromiseを使用します。

Promiseの使用:then、catch、finally

1)then()メソッド

Promiseが満たされたときの値を取得するには、Promiseオブジェクトのthen()メソッドを呼び出します。以下に、then()メソッドの構文を示します。

promise.then(onFulfilled,onRejected);Code language: CSS (css)

then()メソッドは、onFulfilledonRejectedの2つのコールバック関数を受け入れます。

then()メソッドは、Promiseが満たされた場合は値でonFulfilled()を呼び出し、Promiseが拒否された場合はエラーでonRejected()を呼び出します。

onFulfilledonRejectedの両方の引数はオプションであることに注意してください。

次の例は、getUsers()関数によって返されるPromiseオブジェクトのthen()メソッドを使用する方法を示しています。

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { username: 'john', email: '[email protected]' },
        { username: 'jane', email: '[email protected]' },
      ]);
    }, 1000);
  });
}

function onFulfilled(users) {
  console.log(users);
}

const promise = getUsers();
promise.then(onFulfilled);Code language: JavaScript (javascript)

出力

[
  { username: 'john', email: '[email protected]' },
  { username: 'jane', email: '[email protected]' }
]Code language: JavaScript (javascript)

この例では

  • まず、Promiseが満たされたときに呼び出されるonFulfilled()関数を定義します。
  • 次に、getUsers()関数を呼び出して、Promiseオブジェクトを取得します。
  • 3番目に、Promiseオブジェクトのthen()メソッドを呼び出し、ユーザーリストをコンソールに出力します。

コードをより簡潔にするために、次のようにthen()メソッドの引数としてアロー関数を使用できます。

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { username: 'john', email: '[email protected]' },
        { username: 'jane', email: '[email protected]' },
      ]);
    }, 1000);
  });
}

const promise = getUsers();

promise.then((users) => {
  console.log(users);
});
Code language: JavaScript (javascript)

getUsers()関数はPromiseオブジェクトを返すため、次のようにthen()メソッドで関数呼び出しをチェーンできます。

// getUsers() function
//...

getUsers().then((users) => {
  console.log(users);
});
Code language: JavaScript (javascript)

この例では、getUsers()関数は常に成功します。エラーをシミュレートするために、次のようにsuccessフラグを使用できます。

let success = true;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: '[email protected]' },
          { username: 'jane', email: '[email protected]' },
        ]);
      } else {
        reject('Failed to the user list');
      }
    }, 1000);
  });
}

function onFulfilled(users) {
  console.log(users);
}
function onRejected(error) {
  console.log(error);
}

const promise = getUsers();
promise.then(onFulfilled, onRejected);Code language: JavaScript (javascript)

仕組み。

まず、success変数を定義し、その値をtrueに初期化します。

成功がtrueの場合、getUsers()関数のPromiseはユーザーリストで満たされます。それ以外の場合は、エラーメッセージで拒否されます。

次に、onFulfilled関数とonRejected関数を定義します。

3番目に、getUsers()関数からPromiseを取得し、onFulfilled関数とonRejected関数を使用してthen()メソッドを呼び出します。

以下は、then()メソッドの引数としてアロー関数を使用する方法を示しています。

// getUsers() function
// ...

const promise = getUsers();
promise.then(
  (users) => console.log,
  (error) => console.log
);Code language: JavaScript (javascript)

2)catch()メソッド

Promiseの状態が拒否された場合にのみエラーを取得したい場合は、Promiseオブジェクトのcatch()メソッドを使用できます。

promise.catch(onRejected);Code language: CSS (css)

内部的には、catch()メソッドはthen(undefined, onRejected)メソッドを呼び出します。

次の例では、エラーシナリオをシミュレートするために、successフラグをfalseに変更します。

let success = false;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: '[email protected]' },
          { username: 'jane', email: '[email protected]' },
        ]);
      } else {
        reject('Failed to the user list');
      }
    }, 1000);
  });
}

const promise = getUsers();

promise.catch((error) => {
  console.log(error);
});Code language: JavaScript (javascript)

3)finally()メソッド

場合によっては、Promiseが満たされたか拒否されたかに関係なく、同じコードを実行したい場合があります。例:


const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
    render();
  })
  .catch((error) => {
    console.log(error);
    render();
  });Code language: JavaScript (javascript)

ご覧のとおり、render()関数の呼び出しは、then()メソッドとcatch()メソッドの両方で重複しています。

この重複を削除し、Promiseが満たされたか拒否されたかに関係なくrender()を実行するには、次のようにfinally()メソッドを使用します。


const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    render();
  });
Code language: JavaScript (javascript)

JavaScript Promiseの実践的な例

次の例は、サーバーからJSONファイルをロードし、その内容をWebページに表示する方法を示しています。

次のJSONファイルがあるとします。

https://javascripttutorial.dokyumento.jp/sample/promise/api.jsonCode language: JavaScript (javascript)

次の内容で

{
    "message": "JavaScript Promise Demo"
}Code language: JSON / JSON with Comments (json)

以下は、ボタンを含むHTMLページを示しています。ボタンをクリックすると、ページはJSONファイルからデータをロードし、メッセージを表示します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JavaScript Promise Demo</title>
    <link href="css/style.css" rel="stylesheet">
</head>
<body>
    <div id="container">
        <div id="message"></div>
        <button id="btnGet">Get Message</button>
    </div>
    <script src="js/promise-demo.js">
    </script>
</body>
</html>Code language: HTML, XML (xml)

以下は、promise-demo.jsファイルを示しています。

function load(url) {
  return new Promise(function (resolve, reject) {
    const request = new XMLHttpRequest();
    request.onreadystatechange = function () {
      if (this.readyState === 4 && this.status == 200) {
        resolve(this.response);
      } else {
        reject(this.status);
      }
    };
    request.open('GET', url, true);
    request.send();
  });
}

const url = 'https://javascripttutorial.dokyumento.jp/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
  load(URL)
    .then((response) => {
      const result = JSON.parse(response);
      msg.innerHTML = result.message;
    })
    .catch((error) => {
      msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
    });
});
Code language: JavaScript (javascript)

仕組み。

まず、XMLHttpRequestオブジェクトを使用してサーバーからJSONファイルをロードするload()関数を定義します。

function load(url) {
  return new Promise(function (resolve, reject) {
    const request = new XMLHttpRequest();
    request.onreadystatechange = function () {
      if (this.readyState === 4 && this.status == 200) {
        resolve(this.response);
      } else {
        reject(this.status);
      }
    };
    request.open('GET', url, true);
    request.send();
  });
}Code language: JavaScript (javascript)

実行者では、HTTPステータスコードが200の場合、Responseでresolve()関数を呼び出します。それ以外の場合は、HTTPステータスコードでreject()関数を呼び出します。

2番目に、ボタンのクリックイベントリスナーを登録し、Promiseオブジェクトのthen()メソッドを呼び出します。ロードが成功した場合は、サーバーから返されたメッセージを表示します。それ以外の場合は、HTTPステータスコード付きのエラーメッセージを表示します。


const url = 'https://javascripttutorial.dokyumento.jp/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
  load(URL)
    .then((response) => {
      const result = JSON.parse(response);
      msg.innerHTML = result.message;
    })
    .catch((error) => {
      msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
    });
});
Code language: JavaScript (javascript)

概要

  • Promiseは、非同期操作の結果をカプセル化するオブジェクトです。
  • Promiseは、保留中の状態で始まり、満たされた状態または拒否された状態のいずれかで終了します。
  • Promiseが満たされたときに実行されるコールバックをスケジュールするにはthen()メソッドを使用し、Promiseが拒否されたときに呼び出されるコールバックをスケジュールするにはcatch()メソッドを使用します。
  • Promise が成功したか拒否されたかに関わらず、実行したいコードを finally() メソッド内に記述します。

クイズ

このチュートリアルは役に立ちましたか?