Promise(executor)
- コア
- オプショナル
- ツール
説明
ES6 PromiseのPolyfillです。
Promiseは非同期処理とともに使う機構です。
シグニチャ
promise = new Promise(executor)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
executor |
(Function, Function) -> any |
Yes | Promiseがどういった理由で解決・破棄されるのかを決定する関数 |
返り値 | Promise |
Promiseを返します |
executor
executor(resolve, reject)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
resolve |
any -> any |
No | 呼び出すとPromiseが解決する関数 |
reject |
any -> any |
No | 呼び出すとPromiseが破棄する関数 |
返り値 | 返り値は無視されます |
静的メンバー
Promise.resolve
promise = Promise.resolve(value)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
value |
any |
No | 解決したPromsieが提供する値 |
返り値 | Promise |
解決してvalue を返すPromise |
Promise.reject
promise = Promise.reject(value)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
value |
any |
No | 破棄される時に通知される値 |
返り値 | Promise |
value を破棄理由として渡されるPromise |
'Promise.all
promise = Promise.all(promises)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
promises |
Array<Promise|any> |
Yes | 解決を待つPromiseのリストもし要素がPromiseでなければ、その値に対して即座にPromise.resolve を呼び出すPromiseと等価 |
返り値 | Promise |
引数に渡されたすべてのpromises が解決した時、あるいはどれか1つでも破棄したときに解決されるPromise |
Promise.race
promise = Promise.race(promises)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
promises |
Array<Promise|any> |
Yes | 解決を待つPromiseのリストもし要素がPromiseでなければ、その値に対して即座にPromise.resolve を呼び出すPromiseと等価 |
返り値 | Promise |
promises のうちのどれか1つが解決・破棄されたときに解決されるPromise |
インスタンスメンバー
promise.then
nextPromise = promise.then(onFulfilled, onRejected)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
onFulfilled |
any -> (any|Promise) |
No | Promiseが解決されたときに呼び出される関数。この関数の最初の引数はこのPromiseが解決したときに決定する(次のPromiseに渡る)値です。もし関数の返り値がPromiseでなければ、その値はnextPromise の解決のために使われます。もしPromiseだった場合には、nextPromise の受け取る結果はその内部のPromiseに依存します。もし関数が例外を投げると、nextPromise は拒絶され、理由としてその例外が渡されます。onFulfilled がnull なら無視されます。 |
onRejected |
any -> (any|Promise) |
No | Promiseが拒絶されたときに呼び出される関数。この関数の最初の引数はこのPromiseが拒絶したときに決定する(次のPromiseに渡る)値です。もし関数の返り値がPromiseでなければ、その値はnextPromise の解決のために使われます。もしPromiseだった場合には、nextPromise の受け取る結果はその内部のPromiseに依存します。もし関数が例外を投げると、nextPromise は拒絶され、理由としてその例外が渡されます。onRejected がnull なら無視されます。 |
返り値 | Promise |
現在のPromiseの状態に依存した値を持つPromise |
promise.catch
nextPromise = promise.catch(onRejected)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
onRejected |
any -> (any|Promise) |
No | Promiseが拒絶されたときに呼び出される関数。この関数の最初の引数はこのPromiseが拒絶したときに決定する(次のPromiseに渡る)値です。もし関数の返り値がPromiseでなければ、その値はnextPromise の解決のために使われます。もしPromiseだった場合には、nextPromise の受け取る結果はその内部のPromiseに依存します。もし関数が例外を投げると、nextPromise は拒絶され、理由としてその例外が渡されます。onRejected がnull なら無視されます。 |
返り値 | Promise |
現在のPromiseの状態に依存した値を持つPromise |
どのように動作するのか
Promiseは将来得られる値を表現するオブジェクトです。
// 1秒後に解決するPromise
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("hello")
}, 1000)
})
promise.then(function(value) {
// 1秒後に "hello" と出力
console.log(value)
})
Promiseはm.request
などの非同期APIとともに使うと便利です。
一般的に、非同期APIは実行の長い時間がかかります。通常の関数のreturn
文で値を返すのは現実的ではありません。その代わりに、それらのAPIはバックグラウンドで動作するため、その間に他のJavaScriptのコードが実行できます。それらのAPIが完了すると、取得した値をパラメータに乗せて指定された関数を実行するのが良くある形態です。
m.request
関数はリモートサーバーに対してHTTPリクエストをを送り、その結果を待つ必要があります。ネットワークのレイテンシーによって数ミリ秒単位の遅延が発生します。
Promiseチェーン
Promiseは繋げてチェーンさせることができます。then
コールバックから値を返すと、次のthen
コールバックで引数として受け取れます。これにより、長いコールバックを使ったコードを、小さな単位の複数の関数にリファクタリングできます。
function getUsers() {return m.request("/api/v1/users")}
// 避けるべきコード: god関数のテストが難しい
getUsers().then(function(users) {
var firstTen = users.slice(0, 9)
var firstTenNames = firstTen.map(function(user) {return user.firstName + " " + user.lastName})
alert(firstTenNames)
})
// 望ましいコード: 小さい関数のテストはしやすい
function getFirstTen(items) {return items.slice(0, 9)}
function getUserName(user) {return user.firstName + " " + user.lastName}
function getUserNames(users) {return users.map(getUserName)}
getUsers()
.then(getFirstTen)
.then(getUserNames)
.then(alert)
リファクタリングしたコードでは、getUsers()
はPromiseを返しています。それに対して、3つの関数をチェーンさせています。getUsers()
が解決されると、getFirstTen
関数の最初の引数にユーザーのリストがアサインされて呼ばれます。この関数は10ユーザー分抜き出してリストを返します。getUserNames
は、引数で渡されたユーザーのリストから名前だけを抽出して返します。最終的にユーザー名のリストが表示されます。
最初のオリジナルコードではHTTPリクエストを送信しないと動かせませんし、最後にalert()
を使うため、テストが簡単ではありません。
リファクタリングしたバージョンはgetFirstTen
が、位置ずれのエラーがないか、getUserName
が姓と名の間にスペースがあるかどうかのテストがしやすくなっています。
Promiseの吸収
Promiseは他のPromiseを吸収します。基本的には、これはthen
やcatch
のonFulfilled
やonRejected
onFulfilled
のコールバックの引数としてPromiseを受け渡すことができないということです。この機能により、ネストされたPromisがフラットになり、管理しやすくなります。
function searchUsers(q) {return m.request("/api/v1/users/search", {data: {q: q}})}
function getUserProjects(id) {return m.request("/api/v1/users/" + id + "/projects")}
// 避けるべきコード: 悪夢のピラミッド
searchUsers("John").then(function(users) {
getUserProjects(users[0].id).then(function(projects) {
var titles = projects.map(function(project) {return project.title})
alert(titles)
})
})
// 望ましいコード: フラットなフローのコード
function getFirstId(items) {return items[0].id}
function getProjectTitles(projects) {return projects.map(getProjectTitle)}
function getProjectTitle(project) {return project.title}
searchUsers("John")
.then(getFirstId)
.then(getUserProjects)
.then(getProjectTitles)
.then(alert)
リファクタリングしたコードではgetFirstId
はIDを返します。これはgetUserProjects
の最初の引数として渡されます。順番としては、まず解決するとプロジェクトのリストとなるPromiseを返します。このPromiseは吸収されるため、getProjectTitles
の最初の引数はPromiseではなく、プロジェクトのリストとなります。getProjectTitles
関数はタイトルのリストを返し、このリストが最終的に表示されます。
エラーハンドリング
Promiseは適切なハンドラーにエラーを伝搬させることができます。
searchUsers("ジョン")
.then(getFirstId)
.then(getUserProjects)
.then(getProjectTitles)
.then(alert)
.catch(function(e) {
console.log(e)
})
これは先ほどのコードにエラーハンドリングを追加したものです。searchUsers
関数は、例えばネットワークがオフラインの場合に失敗し、エラーを返します。この時は、.then
コールバックが呼ばれることはなく、.catch
コールバックが呼ばれてコンソールにエラーが表示されます。
もしgetUserProjects
内のリクエストが失敗するとgetProjectTitles
とalert
も呼ばれません。.catch
コールバックは呼ばれてエラーがログに表示されます。
searchUsers
に該当する結果がない時は空の配列が帰り、getFirstId
が存在しない配列要素のid
プロパティを取得しにいくためにnull参照例外が発生しますが、エラーハンドラーはこれもキャッチできます。
このエラー伝達のセマンティクスにより、try
/catch
ブロックをいろいろなところに挟み込まなくても良くなるため、各関数が小さく、テスト可能になります。
短縮表記
時々、すでに解決する値はあるが、Promiseでこれをラップしたいことがあります。Promise.resolve
とPromise.reject
を使うと、これが実現できます。
// localStorageから来たリストをサポート
var users = [{id: 1, firstName: "John", lastName: "Doe"}]
// この場合localStorageにデータが有るかどうかで `users` が存在するか決まる
var promise = users ?Promise.resolve(users) : getUsers()
promise
.then(getFirstTen)
.then(getUserNames)
.then(alert)
複数のPromise
複数のHTTPリクエストを並行で投げ、すべてが完了したことを待って何かコードを実行したいことがあります。Promise.all
を使うとこれが実現できます。
Promise.all([
searchUsers("ジョン"),
searchUsers("メアリー"),
])
.then(function(data) {
// data[0]には名前がジョンのユーザーが入る
// data[1]には名前がメアリーのユーザーが入る
// 返り値は次の値と同じ: [
// getUserNames(data[0]),
// getUserNames(data[1]),
// ]
return data.map(getUserNames)
})
.then(alert)
上記のコードでは2つの検索が平行して行われます。両方が完了するとすべてのユーザー名が表示されます。
上記のサンプルには他のメリットも説明されています。上記で作ったgetUserNames
関数を再利用することができます。
なぜコールバックではないのか
コールバックは非同期計算を行うための別のメカニズムです。 onscroll
などのように、複数回実行される前提であればこちらの方が適切でしょう。
しかし、一回のアクションに対して一度しか実行されないのであれば、こちらのほうが有用です。状態が十分に管理されておらず、さまざまなネストの深さのクロージャーがあるような、深い一連のコールバックをもたらす悪夢のピラミッドと呼ばれるコードの臭いをリファクタリングによって効率的に解決できます。
さらに、Promiseを使うとエラーハンドリングのための定型文を減らすことができます。
License: MIT. © Leo Horie.