この記事では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をご覧ください。