10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

忙しい人のための JavaScript Promise(ブラウザで今すぐ実験)

Last updated at Posted at 2018-07-27

##とりあえず打ち込みましょう

F12キーを叩いてブラウザの開発者環境を開いて、コンソールにvar p = fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')と打ち込みます。

image.png

ここでは、 fetch関数を使って指定したサーバーのエンドポイント(Azure Functionでテスト用として立てました)にリクエストを送信して、サーバーからのレスポンスを取ってくるようにしています。

実はこのエンドポイント、delayというパラメータを渡すことでサーバー側でわざと時間がかかるように細工をしています。delay=10の場合10秒間スリープ状態にさせて、それからJSONのデータを含むレスポンスを返すようにしています。反応が悪いAPIをシミュレートしているわけです。

レスポンスが返ってくるまで最低でも10秒かかるはずのコードなのに「一瞬」で変数pに戻り値が格納されました。開発環境コンソールが10数秒固まってしまう、というようなことにもなりませんでした。なぜなら、変数pにはレスポンスではなく、「レスポンスの結果にいつでもアクセスできますよ」というPromise(約束)が代わりに格納されたからです。

##「10秒間経ってしまう前」と「10秒間経ってしまった後」のPromise

fetch()を呼び出してすぐ(10秒以内)、そして10秒以上待ったあとにもう一度、pの値をコンソールに表示してみましょう。

image.png

1回目と2回目の違いは・・・

  • 「10秒間経ってしまう前」pendingというステータスで、responseというプロパティはない
  • 「10秒間経ってしまった後」resolvedというステータスで、responseというプロパティがある

このようにPromiseは「ある仕事を任された請負人との約束」のように機能し、任された仕事の進捗状況と、さらに仕事が終わったならその結果へのアクセスを可能にする、という特徴があります。

##ここまでのおさらい

  • fetch関数のように外部(サーバーなど)の応答を待つ関数はPromiseを返すことが多い
  • 呼び出し元は、そのような関数からPromiseをすぐに受け取り、ブロックされることはない
  • Promiseには状態があって、時間の経過とともに変化する
  • Promiseのスタータスがpendingなら、仕事はまだ終わっていない
  • Promiseのステータスがresolvedなら、仕事は終わっていて、結果へのアクセスが出来る

Promise の結果を受け取る

Promiseから仕事の結果を受け取るコードを書いてみましょう。

image.png

Promiseのthen()という関数を呼び出しています。then()には、Promiseの結果(この場合はresponse)を受け取って処理するための関数を渡しています。

処理はresponseクラスのjson()というメソッドを呼び出すことで、レスポンスからJSONペイロードを抽出するようにしています。その結果をさらに返して、p2という別の変数に格納しました。

ここでp2もPromiseであることに注意してください。これはresponseクラスの仕様によると、json()メソッドもPromiseを返すとあります。

Responseクラスのメソッドのうち、ストリームを読むメソッドは全部Promiseを返します。ストリームを読むという「仕事」も時間がかかる場合があるのでPromiseの仕組みを利用しているというわけです。この種の関数やメソッドはPromiseを多用していてよく見かけます。

では最後にp2に格納されているPromiseの結果を受け取りに行きましょう。

image.png

ようやくAPIからのレスポンスに含まれていたJSONデータを取得できました。

##ここまでのコードのまとめ

下の3行のコードをそのままコンソールにコピペしてみましょう。

var p = fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
var p2 = p.then(function(response) { return response.json(); })
p2.then(function(json) { console.log(json); })

結果とどうなりましたか?10秒ほどたってから突然、JSONのデータがコンソールに表示されたはずです。

image.png

ここで重要なのは、このコードは2つのPromise(この場合pp2)の状態を気にせずに書くことが出来ることです。それぞれのPromiseが一瞬で終わろうとも、数10秒かかろうとも(エラー発生の場合は除く。エラー対策に関しては後述)同じコードです。

##コードをさらに整理する

then()関数はPromiseを返すので、変数pp2を省略してひとつの制御文に整理することができます。

fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
.then(function(response) { return response.json(); })
.then(function(json) { console.log(json); })

結果は全く同じで、10秒後にJSONデータが表示されました。ここでは、fetch()関数が返すPromiseのthen()関数を呼び出し、その戻り値であるPromiseに対して、さらにthen()関数を呼び出す、という風にチェーンにしています。

チェーンの順番が、実行の順番と同じなのでより自然に感じられます。

##エラーへの対応

ところで、例えばサーバーがデータを返さなかったり、JSONのデータが壊れていてパース出来ない、といった状況の場合はいったいどうしたらいいのでしょう?特に複数のPromiseをチェーンにしている時は、どこでエラーが起こるやもしれません。

ということで、エラーハンドラーを仕掛けましょう。catch()関数を最後に呼び出すことで、一種の例外処理を行います。

fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
.then(function(response) { return response.json(); })
.then(function(json) { console.log(json); })
.catch(function(err) { console.log("something went wrong: " + err.message); })

サーバーをいったんオフラインにして、実行してみるとこのような結果になりました。

image.png

これはjson()メソッドが「パースするJSONが無いんですけど」と文句を言っている状況です。

ちなみに、サーバーをOFFにしたので404が返ってきましたが、これは1つめのPromiseのステータスがrejectedになったわけではありません。ちゃんと404のレスポンスという「仕事の結果」が得られたのでPromiseのステータスもresolvedになっています。この例ではこの1つ目のPromiseの結果の処理において、エラーが発生した、ということです。

##おさらい、その2

  • Promiseから仕事の結果を得るにはthen()関数を呼び出す
  • then()関数には、結果を受け取る関数を定義して渡す
  • Promiseの仕組みはネットワークへのアクセスやI/O関連のAPIで多用されている
  • Promiseの仕組みを使うと「いつ仕事の結果が得られる状態になるか」を気にせずにコードを書ける。その時が来たらthen()に渡した関数が実行を始める。それは0.1ミリ秒後かもしれないし、10秒後かもしれない。
  • then()はPromiseを返すので、一連の処理をチェーンにして正しい順序を指定することが出来る
  • チェーンにした一連の処理は書く順序と実行する順序が同じなので自然で読みやすい
  • エラー対策としてcatch()関数を利用できる

##発展

この記事ではPromiseの利用に関して書きました。Promiseの作り方についてはまた別の機会で(もしくはここを拡張するかもしれません)。

ここまでの内容がわかったらPromise.allの関数を見ると楽しいでしょう。

##おまけ

アロー関数式を使ってさらに整理するともっとスッキリしますね。Promiseとは関係ないですが。

fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
.then(response => { return response.json(); })
.then(json => { console.log(json); })
.catch(err => { console.log("something went wrong: " + err.message); })
10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?