この記事ではasync/awaitを扱いません。
Promiseとは
Promiseは非同期処理を扱うためのクラスです。
非同期とは、実行に長時間かかるなどの理由で、処理の実行完了を待たずに次の処理ができるような仕組みのことです。
対義語は「同期」で、こちらは実行が完了するまで次にいけないような通常の処理を示します。
一部の関数やメソッドはPromiseオブジェクトを返します。
JavaScriptに組み込まれているAPIでは、例えばfetch関数があります。
使い方
ここではHTTPリクエストを送るfetch関数を例に説明します。
fetch関数は、Promiseを返さない関数と同じように呼び出すことができます。
しかし、戻り値はHTTPリクエストのレスポンスではなく、Promiseオブジェクトです。
const promise = fetch('https://example.com')
promise // Promise
fetch関数の呼び出しはさほど時間がかからずに終了します。
レスポンスが返ってくるにはそれなりの時間がかかるのにも関わらずです。
実は、fetch関数の呼び出しはレスポンスが返るのを待たずに終了します。
そして、レスポンスが返ってきたのかどうか、レスポンスの内容は、戻り値のPromiseオブジェクトから確認できます。
ステータス
Promiseオブジェクトには、そのプロミスの現在の状態を示すステータスがあります。
ステータスには主に3つの種類があります。
- 待機 (
pending): 初期状態、処理中 - 履行 (
fulfilled): 処理が成功した - 拒否 (
rejected): 処理が失敗した
then
thenメソッドを使うと、プロミスが履行された=fulfilledになったときの処理を登録することができます。
fetch関数で使うなら、HTTPリクエストのレスポンスが返ってきて、その内容を使って処理したいときになります。
例えば、レスポンスヘッダの内容(response.headers)をコンソールに出力してみます。
fetch('https://example.com')
.then(response => console.log(response.headers))
// responseにはResponseオブジェクトが入る
// 出力
Headers {
"content-encoding": "gzip",
"age": "233565",
"cache-control": "max-age=604800",
// 略
}
こちらのコードをブラウザのコンソールなどから実行した場合、CORSポリシーに違反しているためPromiseが拒否(rejected)されてしまうことがあります。
その場合、Node.jsなどの環境から実行するか、https://example.com のコンソールから実行してみてください。
チェイン
また、Responseオブジェクトからレスポンスボティを取り出すには、textメソッドを使います。
しかし、textはPromiseを返します。
そのためひとつのthenの中にtextの結果を使う処理は書けません。
fetch('https://example.com') // プロミスを返す
.then(response => {
const body = response.text() // textの戻り値はまた別のプロミス
console.log(body) // Promiseオブジェクトが出力されてしまう
})
そんなときに、thenメソッドのチェインが使えます。
チェインというのは、thenの呼び出しの後にさらにthenを呼び出すことです。
fetch('https://example.com')
.then(response => response.text()) // 次のthenはこれが履行されるまで処理されない
.then(body => console.log(body))
// レスポンスボディが出力される
なぜこれがができるのかと言うと、thenメソッドが戻り値として新しいPromiseオブジェクトを返すからです。
このコードでは4つのプロミスが作成されます。
-
fetch関数の戻り値のプロミス- 履行タイミング: レスポンスが返ってきたとき
- 履行されたときの値: レスポンスを示す
Responseオブジェクト
- 1の
Responseオブジェクトのtextメソッドの戻り値- 履行タイミング: レスポンスボディの計算が完了したとき
- 履行されたときの値: レスポンスボディを示す文字列
- 1つ目の
then- 履行タイミング: 1が履行され、2が履行されたとき
- 履行されたときの値: 2と同じ
- 2つ目の
then- 履行タイミング: レスポンスボディのログ出力が終わったとき
- 履行されたときの値:
undefined(console.logの戻り値)
Promiseの詳細はこちらの記事がわかりやすかったです。
Promiseオブジェクトを作成する
Promiseオブジェクトはコンストラクタを使うことで作ることができます。
コンストラクタは引数に関数を取ります。
この関数は、Promiseオブジェクトが作られたときに実行される処理で、またどのタイミングでプロミスを履行 / 拒否するのかを制御します。
new Promise((resolve, reject) => {
// この処理はすぐに実行される
// プロミスを履行したいときにresolveを呼ぶ
resolve()
// プロミスを拒否したいときにrejectを呼ぶ
reject()
})
なお、Promiseオブジェクトは基本的に使い捨てです。
同じオブジェクトを何度も使うのではなく、必要になったときに新しく作ります。
そのためAPIを作るときは、Promiseオブジェクトのそのものではなく、プロミスを返す関数を定義することが多いです。
作例: sleep
ここでは、setTimeoutを使いやすくラップしたsleep関数を作ります。
setTimeoutは、一定時間後に特定の処理を実行する関数です。
引数にコールバック関数と待つ時間を取ります。
setTimeout(() => {
console.log('Hello World!')
}, 1000) // 1000ms後に実行する
// 1000ms後
// Hello World!
sleep関数は指定した時間処理を待機する関数です。
引数msに待つ時間(ミリ秒)を指定し、その時間が経ったら履行されるプロミスを返します。
実装は割と単純です。
setTimeoutを使って一定時間経ったらresolveを呼び出すだけです。
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
sleep(1000)
.then(() => console.log('Hello World!'))
// 1000ms後
// Hello World!
また、このsleep関数はasync/awaitと組み合わせることで真価を発揮します。
await sleep(1000) // 1000ms秒待つ
console.log('Hello World!')
// 1000ms後
// Hello World!
詳細はMDNをご覧ください。