JavaScript Promises:例を含むチュートリアル
公開: 2022-03-11約束はJavaScript開発サークルのホットトピックであり、あなたは間違いなくそれらに精通している必要があります。 彼らはあなたの頭を包むのは簡単ではありません。 それらを理解するには、いくつかのチュートリアル、例、およびかなりの量の練習が必要になる場合があります。
このチュートリアルの私の目的は、JavaScript Promisesを理解し、それらをさらに使用する練習をするように促すことです。 約束とは何か、それらが解決する問題、そしてそれらがどのように機能するかを説明します。 この記事で説明する各ステップには、作業を支援し、さらに調査するためのベースとして使用できるjsbin
コード例が付属しています。
JavaScriptの約束とは何ですか?
約束は、最終的に価値を生み出す方法です。 これは、getter関数の非同期の対応物と見なすことができます。 その本質は次のように説明できます。
promise.then(function(value) { // Do something with the 'value' });
Promiseは、コールバックの非同期使用に取って代わることができ、それらに比べていくつかの利点があります。 非同期性を処理するための主要な方法として、ますます多くのライブラリとフレームワークがそれらを採用するにつれて、それらは定着し始めます。 Ember.jsは、そのようなフレームワークの優れた例です。
Promises /A+仕様を実装するライブラリがいくつかあります。 基本的な語彙を学び、JavaScriptのpromiseの例をいくつか紹介して、その背後にある概念を実用的な方法で紹介します。 コード例では、より一般的な実装ライブラリの1つであるrsvp.jsを使用します。
準備をしなさい、私達はたくさんのサイコロを振るでしょう!
rsvp.jsライブラリの取得
Promise、つまりrsvp.jsは、サーバー側とクライアント側の両方で使用できます。 nodejsにインストールするには、プロジェクトフォルダーに移動して次のように入力します。
npm install --save rsvp
フロントエンドで作業してバウアーを使用する場合、それは単なる
bower install -S rsvp
あちらへ。
ゲームに参加したいだけの場合は、単純なスクリプトタグを使用して含めることができます( jsbin
を使用すると、[ライブラリの追加]ドロップダウンから追加できます)。
<script src="//cdn.jsdelivr.net/rsvp/3.0.6/rsvp.js"></script>
約束にはどのような特性がありますか?
約束は、保留中、履行済み、または拒否の3つの状態のいずれかになります。 作成されると、Promiseは保留状態になります。 ここから、実行状態または拒否状態のいずれかになります。 この移行を約束の解決と呼びます。 約束の解決された状態はその最終状態であるため、それが実行または拒否されると、そこにとどまります。
rsvp.jsでpromiseを作成する方法は、いわゆるリビールコンストラクターを使用することです。 このタイプのコンストラクターは、単一の関数パラメーターを受け取り、すぐに2つの引数、 fulfill
とreject
を使用して呼び出します。これにより、promiseをfulfilled
またはrejected
状態に遷移させることができます。
var promise = new RSVP.Promise(function(fulfill, reject) { (...) });
このJavaScriptPromiseパターンは、単一の関数引数がその機能をコンストラクター関数に公開するため、公開コンストラクターと呼ばれますが、Promiseのコンシューマーがその状態を操作できないようにします。
promiseのコンシューマーは、 then
メソッドを介してハンドラーを追加することにより、状態の変化に対応できます。 フルフィルメントと拒否ハンドラー関数が必要ですが、どちらも欠落している可能性があります。
promise.then(onFulfilled, onRejected);
promiseの解決プロセスの結果に応じて、 onFulfilled
またはonRejected
ハンドラーのいずれかが非同期的に呼び出されます。
物事が実行される順序を示す例を見てみましょう。
function dieToss() { return Math.floor(Math.random() * 6) + 1; } console.log('1'); var promise = new RSVP.Promise(function(fulfill, reject) { var n = dieToss(); if (n === 6) { fulfill(n); } else { reject(n); } console.log('2'); }); promise.then(function(toss) { console.log('Yay, threw a ' + toss + '.'); }, function(toss) { console.log('Oh, noes, threw a ' + toss + '.'); }); console.log('3');
このスニペットは、次のような出力を出力します。
1 2 3 Oh, noes, threw a 4.
または、運が良ければ、次のようになります。
1 2 3 Yay, threw a 6.
このpromisesチュートリアルは、2つのことを示しています。
まず、promiseにアタッチしたハンドラーは、他のすべてのコードが非同期で実行された後に実際に呼び出されました。
第二に、履行ハンドラーは、約束が履行されたときにのみ呼び出され、それが解決された値(この場合はサイコロを投げた結果)を使用します。 同じことが拒否ハンドラーにも当てはまります。
約束を連鎖させ、滴り落ちる
この仕様では、 then
関数(ハンドラー)もpromiseを返す必要があります。これにより、promiseをチェーン化できるため、コードはほぼ同期しているように見えます。
signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage)
ここで、 signupPayingUser
はpromiseを返し、promiseチェーン内の各関数は、完了すると前のハンドラーの戻り値で呼び出されます。 すべての実用的な目的で、これはメインの実行スレッドをブロックすることなく呼び出しをシリアル化します。
各promiseがチェーン内の前のアイテムの戻り値でどのように解決されるかを確認するために、サイコロを投げることに戻ります。 サイコロを最大3回、または最初の6つがjsbinになるまで投げたいと思います。
function dieToss() { return Math.floor(Math.random() * 6) + 1; } function tossASix() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; if (n === 6) { fulfill(n); } else { reject(n); } }); } function logAndTossAgain(toss) { console.log("Tossed a " + toss + ", need to try again."); return tossASix(); } function logSuccess(toss) { console.log("Yay, managed to toss a " + toss + "."); } function logFailure(toss) { console.log("Tossed a " + toss + ". Too bad, couldn't roll a six"); } tossASix() .then(null, logAndTossAgain) //Roll first time .then(null, logAndTossAgain) //Roll second time .then(logSuccess, logFailure); //Roll third and last time
このpromisesサンプルコードを実行すると、コンソールに次のようなものが表示されます。
Tossed a 2, need to try again. Tossed a 1, need to try again. Tossed a 4. Too bad, couldn't roll a six.
tossASix
によって返されるpromiseは、tossが6でない場合に拒否されるため、拒否ハンドラーは実際のtossで呼び出されます。 logAndTossAgain
は、その結果をコンソールに出力し、別のサイコロを投げることを表すpromiseを返します。 そのトスも拒否され、次のlogAndTossAgain
によってログアウトされます。

ただし、運が良ければ*、6を出すことができます。
Tossed a 4, need to try again. Yay, managed to toss a 6.
*あなたはその幸運を得る必要はありません。 3つのサイコロを振った場合、少なくとも1つの6を振る確率は約42%です。
その例はまた、私たちにもっと何かを教えてくれます。 6の最初のローリングが成功した後、どのようにトスが行われなくなったかを確認してください。 チェーン内のすべてのフルフィルメントハンドラー( then
への呼び出しの最初の引数)は、最後のlogSuccess
を除いてnull
であることに注意してください。 仕様では、ハンドラー(履行または拒否)が関数でない場合、返されたPromiseを同じ値で解決(履行または拒否)する必要があります。 上記のpromiseの例では、フルフィルメントハンドラーnull
は関数ではなく、promiseの値は6で実行されました。したがって、 then
呼び出し(チェーン内の次の呼び出し)によって返されるpromiseも実行されます。値として6を使用します。
これは、実際のフルフィルメントハンドラー(関数であるもの)が存在するまで繰り返されるため、フルフィルメントは処理されるまで細流になります。 私たちの場合、これはチェーンの最後で発生し、コンソールに元気にログアウトされます。
エラーの処理
Promises / A +仕様では、Promiseが拒否された場合、または拒否ハンドラーでエラーがスローされた場合、ソースの「下流」にある拒否ハンドラーで処理する必要があります。
以下のトリクルダウン手法を活用すると、エラーを処理するためのクリーンな方法が得られます。
signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage) .then(null, displayAndSendErrorReport)
拒否ハンドラーはチェーンの最後にのみ追加されるため、チェーン内のいずれかのフルフィルメントハンドラーが拒否されたり、エラーがスローされたりすると、 displayAndSendErrorReport
にぶつかるまで細流になります。
私たちの最愛のサイコロに戻って、それが実際に動いているのを見てみましょう。 サイコロを非同期で投げて結果を出力したいとします。
var tossTable = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six' }; function toss() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; fulfill(n); }); } function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUppercase() + "."); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain);
これを実行しても、何も起こりません。 コンソールには何も印刷されず、エラーもスローされないようです。
実際には、エラーがスローされます。チェーンに拒否ハンドラーがないため、エラーは表示されません。 ハンドラー内のコードは、新しいスタックを使用して非同期で実行されるため、コンソールにログアウトされることもありません。 これを修正しましょう:
function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUpperCase() + "."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain) .then(null, logErrorMessage);
上記のコードを実行すると、エラーが表示されます。
"Tossed a TWO." "Oops: Cannot read property 'toUpperCase' of undefined"
logAndTossAgain
から何かを返すのを忘れ、2番目の約束はundefined
で実行されます。 次に、次のフルフィルメントハンドラーが、その上でtoUpperCase
を呼び出そうとして爆発します。 これは、覚えておくべきもう1つの重要なことです。常にハンドラーから何かを返すか、後続のハンドラーで何も渡されないように準備してください。
より高く構築する
このチュートリアルのサンプルコードで、JavaScriptのpromiseの基本を確認しました。 それらを使用することの大きな利点は、それらを簡単な方法で構成して、私たちが望む動作で「複合」の約束を生成できることです。 rsvp.js
ライブラリはそれらのいくつかを提供し、プリミティブとこれらの高レベルのものを使用していつでも独自のものを作成できます。
最後の最も複雑な例として、AD&Dのロールプレイングの世界に移動し、サイコロを投げてキャラクターのスコアを取得します。 このようなスコアは、キャラクターのスキルごとに3つのサイコロを振ることによって得られます。
最初にここにコードを貼り付けてから、新機能について説明します。
function toss() { var n = Math.floor(Math.random() * 6) + 1; return new RSVP.resolve(n); // [1] } function threeDice() { var tosses = []; function add(x, y) { return x + y; } for (var i=0; i<3; i++) { tosses.push(toss()); } return RSVP.all(tosses).then(function(results) { // [2] return results.reduce(add); // [3] }); } function logResults(result) { console.log("Rolled " + result + " with three dice."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } threeDice() .then(logResults) .then(null, logErrorMessage);
最後のコード例のtoss
に精通しています。 それは単にサイコロを振った結果で常に満たされる約束を作成します。 私はRSVP.resolve
を使用しました。これは、より少ないセレモニーでそのような約束を作成する便利な方法です(上記のコードの[1]を参照)。
threeDice
で、それぞれがサイコロを投げることを表す3つのプロミスを作成し、最後にそれらをRSVP.all
と組み合わせました。 RSVP.all
は、promiseの配列を受け取り、それらの順序を維持しながら、構成されたpromiseごとに1つずつ、解決された値の配列で解決されます。 つまり、結果にトスのresults
があり(上記のコードの[2]を参照)、それらの合計で満たされる約束を返します(上記のコードの[3]を参照)。
結果のpromiseを解決すると、総数がログに記録されます。
"Rolled 11 with three dice"
約束を使用して実際の問題を解決する
JavaScriptのpromiseは、理由のない非同期のサイコロを投げるよりもはるかに複雑なアプリケーションの問題を解決するために使用されます。
3つのサイコロを振って3つのajaxリクエストを別々のエンドポイントに送信し、すべてが正常に戻ったとき(またはいずれかが失敗した場合)に続行すると、promiseとRSVP.all
の便利なアプリケーションがすでにあります。
Promiseを正しく使用すると、コールバックよりも推論しやすく、デバッグしやすい読みやすいコードが生成されます。 すでに仕様の一部であるため、エラー処理などの規則を設定する必要はありません。
このJavaScriptチュートリアルで、promiseが実行できることの表面をかろうじてかじっただけです。 Promiseライブラリは、自由に使える多数のメソッドと低レベルのコンストラクターを提供します。 これらをマスターすれば、空はあなたがそれらでできることの限界です。
著者について
Balint Erdiは、昔はロールプレイングとAD&Dの素晴らしいファンでしたが、今では大きな期待とEmber.jsのファンです。 常に変わらないのは、ロックンロールに対する彼の情熱です。 そのため、彼は本のアプリケーションのテーマとしてロックンロールを使用する本をEmber.jsで作成することにしました。 ここでサインアップして、いつ起動するかを確認してください。