理解したい人のための約束ガイド

森は素晴らしいです、暗闇-深く見てください。
しかし、最初に、私はすべての借金を返済します...
そして、私が寝ている間に何マイルも
寝ている間に何マイルも...
ロバート・フロスト

画像

PromiseはES6の最も注目すべき革新の1つです。 JavaScriptは、コールバック関数およびその他のメカニズムを介して非同期プログラミングをサポートします。 ただし、コールバック関数を使用すると、いくつかの問題が発生します。 その中には「 コールバック地獄 」と「 恐怖ピラミッド 」があります。 Promiseは、非同期JSプログラミングを大幅に簡素化するパターンです。 promiseを使用して記述された非同期コードは同期のように見え、コールバックの問題がありません。

本日、私たちが翻訳した資料は、約束とその実用に向けられています。 約束に対処したい初心者開発者向けに設計されています。

約束とは何ですか?


ECMAが提供するプロミスの定義は次のとおりです。「プロミスは、遅延(および場合によっては非同期)計算の将来的な結果のプレースホルダーとして使用されるオブジェクトです。

簡単に言えば、約束は将来の意味のコンテナです。 ここで、約束について言えば、しばしば「約束」および「約束された結果」と呼ばれることに注意する価値があります。 少し考えてみると、人々が日常生活で「約束」という言葉を使うのと似ています。 たとえば、インド行きの飛行機のチケットを予約しました。 そこで、美しいダージリンマウンテンステーションを訪れます。 予約操作が完了すると、チケットが届きます。 実際、このチケットは、航空会社があなたが行きたい日に飛行機の座席を提供するという約束です。 一般に、チケットは将来の重要性、つまり飛行機の座席のプレースホルダーです。

別の例を示します。 あなたはそれを読んだ後、彼の本、 プログラミング芸術を彼に返すと友人に約束しました。 この場合、プレースホルダーはあなたの言葉です。 そして、「将来の結果」は本です。

通常の生活では、他の同様の状況を見つけることができます。 医師のオフィスで待っている、レストランで食事を注文している、図書館で本を発行している、などと言ってください。 これらの状況にはすべて、何らかの形の約束が含まれています。 おそらく、かなり単純な例をいくつか示したので、 コードに入りましょう。

約束の作成


約束は、ある操作を実行するのにどれくらいの時間がかかるかを正確に言うことができない場合、またはこの操作に非常に長い時間がかかることが予想される状況で作成されます。 たとえば、ネットワーク要求には10〜200ミリ秒かかる場合がありますが、これは接続速度によって異なります。 休止状態でこのデータを受信するのを待ちたくありません。 人にとっては200ミリ秒は非常に短いですが、コンピューターにとってはこれは非常に重要な期間です。 約束はそのような問題の解決を単純化し、促進します。

Promiseコンストラクターを使用して、新しいプロミスを作成できます。 こんな感じです。

 const myPromise = new Promise((resolve, reject) => {   if (Math.random() * 100 <= 90) {       resolve('Hello, Promises!');   }   reject(new Error('In 10% of the cases, I fail. Miserably.')); }); 

コンストラクターは、2つのパラメーターを持つ関数を受け入れることに注意してください。 この関数はexecutor関数と呼ばれ、実行する必要がある計算を記述します。 パラメーターはそれぞれresolveおよびrejectと呼ばれ、実行中の関数の成功および失敗の完了を示すために使用されます。

resolveおよびrejectパラメータも関数であり、promiseオブジェクトに値を返すために使用されます。 計算が成功した場合、または将来の値の準備ができている場合、 resolve関数を使用してこの値を送信します。 そのような状況では、彼らは約束の成功した解決について話します。

計算が成功しなかった場合、または操作中にエラーが発生した場合、エラーオブジェクトをreject関数に渡すことでこれを報告rejectます。 この場合、彼らは約束が拒否されたと言います。 実際、 reject関数は任意の値を受け入れますが、 Errorオブジェクトを渡すことをお勧めします。これは、デバッグ中にスタックをトレースするときに役立つためです。

この例では、 Math.random()関数を使用して乱数を生成します。 90%のケースでは、さまざまな乱数を発行する確率が等しいことに基づいて、約束が許可されます。 それ以外の場合、拒否されます。

Promiseの使用


上記では、約束を作成し、そのリンクをmyPromise保存しmyPromiseresolveおよびrejectによって渡された値にアクセスする方法reject ? すべてのpromiseオブジェクトで使用可能な.then()関数は、これを支援します。 彼女と働く方法を見てみましょう。

 const myPromise = new Promise((resolve, reject) => {   if (Math.random() * 100 < 90) {       console.log('resolving the promise ...');       resolve('Hello, Promises!');   }   reject(new Error('In 10% of the cases, I fail. Miserably.')); }); //   const onResolved = (resolvedValue) => console.log(resolvedValue); const onRejected = (error) => console.log(error); myPromise.then(onResolved, onRejected); //   ,      myPromise.then((resolvedValue) => {   console.log(resolvedValue); }, (error) => {   console.log(error); }); //  ( 90% ) // resolving the promise ... // Hello, Promises! // Hello, Promises! 

.then()メソッドは、2つのコールバック関数を受け入れます。 最初のものは、約束が解決されるときに呼び出されます。 約束が拒否された場合、2番目が実行されます。

onResolvedonRejected 2つの関数にonResolvedしてonRejected 。 これらは、コールバック関数の役割で、 .then()メソッドに渡されます。 以下の同じ例に示すように、同じものを短く書くことができます。 そのような設計の機能は、それらが.then()を渡す前に記述された機能の機能と.then()ません。

ここでは、いくつかの重要なことに特に注意を払いたいと思います。 myPromise myPromiseを作成しました。 次に、 .then()ハンドラーを2回アタッチしました。 どちらも同じ機能を備えていますが、異なるエンティティとして認識されます。 この点に関して、次のことに注意する必要があります。


つまり、複数の.then()ハンドラーがpromiseに接続されていても、promiseが最終状態に達するとすぐに、この状態は変化しません(つまり、計算は繰り返し実行されません)。

これを確認するには、例の最初にあるconsole.log()呼び出しを見てください。 コードが開始され、2つの.then()ハンドラーがアタッチされると、 console.log()呼び出しは1回だけ実行されます。 これは、promiseが結果をキャッシュし、別の.then()接続されたときに同じ結果を生成することを示します。

注目すべきもう1つの重要なことは、プロミスが活気のあるコンピューティング戦略を使用することです。 このアプローチでは、promiseの計算は宣言された直後に開始され、そのリンクが変数に書き込まれます。 promiseを強制的に実行するために使用できる.start().begin()などのメソッドはありません。 前の例では、すべてがそのように発生します。

約束を処理するときに遅延計算戦略を使用して、即座に呼び出されないようにし、関数にラップされるようにします。 これについては後で説明します。

promiseのエラー処理


これまでのところ、話を複雑にしないために、約束の成功した解決のケースのみを考慮してきました。 次に、実行中の関数でエラーが発生したときに何が起こるかについて話しましょう。 この状況では、2番目のコールバック.then()が呼び出されます。 onRejectedonRejected関数です。 例を考えてみましょう。

 const myPromise = new Promise((resolve, reject) => { if (Math.random() * 100 < 90) {   reject(new Error('The promise was rejected by using reject function.')); } throw new Error('The promise was rejected by throwing an error'); }); myPromise.then( () => console.log('resolved'), (error) => console.log(error.message) ); //  ( 90% ) // The promise was rejected by using reject function. 

この例は前の例とまったく同じですが、約束が90%の確率で拒否され、残りの10%のケースでエラーが発生するという違いがあります。

onResolvedおよびonRejectedコールバック関数はonRejected宣言されていonRejected 。 promiseコードの実行中にエラーがスローされた場合でも、onRejected onRejectedが実行されることに注意してください。 エラーオブジェクトをreject関数に渡すことにより、プロミスを明示的に拒否する必要はありません。 つまり、両方のケースで約束は拒否されます。

エラー処理は信頼できるプログラムを開発するために必要な条件であるため、Promiseでエラーを処理するための特別なメカニズムが提供されています。 .then(null, () => {...})ようなものを.then(null, () => {...})代わりに、エラーを処理する必要がある場合は、1つのコールバック.catch(onRejected)コンストラクトを使用できます。 これは、このメカニズムを追加すると、上記のコードの新しいフラグメントがどのように見えるかです。

 myPromise.catch( (error) => console.log(error.message) ); 

.catch()は実際には.then(undefined, onRejected)単なる「 構文糖 」であること.then(undefined, onRejected)

連鎖の約束


.then()および.catch()メソッドは常に.catch()を返します。 したがって、複数の.then()呼び出しをチェーンにチェーンできます。 これを例として見てみましょう。

最初に、promiseを返すdelay()関数を作成します。 返された約束は、指定された時間後に解決されます。 これがこの関数の外観です。

 const delay = (ms) => new Promise( (resolve) => setTimeout(resolve, ms) ); 

この例では、関数を使用してプロミスをラップします。その結果、プロミスはすぐには実行されません。 delay()関数は、ミリ秒単位で表される時間をパラメーターとして受け入れます。 実行中の関数は、 クロージャーのためにmsパラメーターにアクセスできます。 ここでは、さらに、指定されたミリ秒数が経過した後にresolved関数を呼び出すsetTimeout()呼び出しがあります。これにより、promiseの解決につながります。 この機能の使用方法は次のとおりです。

 delay(5000).then(() => console.log('Resolved after 5 seconds')); 

複数の.then()呼び出しをチェーンにチェーンする方法は次の.then()です。

 const delay = (ms) => new Promise( (resolve) => setTimeout(resolve, ms) ); delay(2000) .then(() => {   console.log('Resolved after 2 seconds')   return delay(1500); }) .then(() => {   console.log('Resolved after 1.5 seconds');   return delay(3000); }).then(() => {   console.log('Resolved after 3 seconds');   throw new Error(); }).catch(() => {   console.log('Caught an error.'); }).then(() => {   console.log('Done.'); }); // Resolved after 2 seconds // Resolved after 1.5 seconds // Resolved after 3 seconds // Caught an error. // Done. 

このコードは、 delay関数が呼び出される行から始まります。 その後、次のことが発生します。


さらに、 throw new Error()コマンドを実行するコードフラグメントに注意してください。つまり、 .then()エラーをスローします。 これは、現在のプロミスが拒否され、次の.catch()ハンドラーが呼び出されることを意味します。 結果として、文字列Caught an error a Caught an errorがログに表示されます。 .catch()続く.then()ブロックがさらに呼び出されるのはそのためです。

エラー処理に使用することをお勧めします。 onResolvedおよびonRejectedした.then() catch()ではなく、 catch() 。 この推奨事項を明確にするコードを次に示します。

 const promiseThatResolves = () => new Promise((resolve, reject) => { resolve(); }); //   UnhandledPromiseRejection promiseThatResolves().then( () => { throw new Error }, (err) => console.log(err), ); //    promiseThatResolves() .then(() => {   throw new Error(); }) .catch(err => console.log(err)); 

この例の最初に、常に解決される約束を作成します。 onResolvedonRejected 2つのコールバックを持つ.then()がある場合、エラーと、実行中の関数に対してのみプロミスが拒否される状況を処理できます。 .then()ハンドラーもエラーをスローするとします。 これは、コードからわかるように、コールバックonRejectedはつながりません。

ただし、 .then()後に.catch( )ブロックがある場合、このブロックは実行中の関数エラーと.then()エラーの両方をキャッチします。 .then()常にpromiseを返すため、これは理にかなっています。

まとめ


すべての例を個別に実行できます。これにより、練習を通じて、この資料で説明した内容をよりよくマスターできます。 約束を勉強するために、約束の形でコールバックベースの関数の実装を練習できます。 Node.jsで作業する場合は、 fsおよび他のモジュールの多くの関数がコールバックに基づいていることに注意してください。 そのような機能を約束に基づいた設計に自動的に変換できるユーティリティがあります。 これがNode.jsのutil.promisifypifyであるとしましょう。

ただし、これらすべてを学習している場合は、WET(すべてを2回書く、すべてを2回書く)の原則を順守し、調査中のライブラリに可能な限り多くのコードを実装することをお勧めします(注意深く読むほど)。 それ以外の場合、特に本番に入るコードを書く場合は、DRY原則(自分自身を繰り返さないで、繰り返さないでください)に固執してください。 約束を伴う作業に関する限り、この資料に該当しなかったものがまだたくさんあります。 たとえば、これらは静的メソッドPromise.allPromise.raceなどです。 さらに、ここではエラー処理について簡単に説明します。 約束を処理する際に知っておく必要のある、広く知られているアンチパターンと微妙さがあります。 ECMA仕様Mozilla Docs 、Google Promises Guide、Promisesに関するJSの探索の 、Promisesの基本に関する有用な記事など、これらすべてに関心のある人のためにあなたが見てみたいと思うものがいくつかあります。

親愛なる読者! JavaScriptで非同期コードをどのように記述しますか?

Source: https://habr.com/ru/post/J358808/


All Articles