永遠に機能する:約束を待つ

21157 ワード

著者によってFederico Kereki
約束は、機能的なプログラミング(FP)の概念です-しかし、その領域では、彼らはmonads この論文では、PromiseとHigh - Order関数を通して、エレガントな、短い解決を成し遂げるプログラミング挑戦を解決するために、FPを適用する方法を見ます.
私たちの特定のケースでは、解決する状況はこれでした:何か(外部)が起こって、効率的にこのチェックを実装するまで待ちます.我々が捜しなければならなかった条件の種類は、サービスが上がっていて、走るならば他の可能性は、Webワーカーがファイルを手に入れた場合、十分な時間が経過した場合、または同様の条件の任意の組み合わせが完了した場合、可能性があります.( APIコールは特別な考慮を必要としません.rxjsのようないくつかのライブラリを使用して、この種のチェックは反応プログラミングで解決されることができます.しかし、この記事では、このようないくつかのFPテクニックを使用して完全性の成長順序で、この“待機問題”のいくつかの解決策が表示されます.

ジャスト・ループ


始めましょうかcondition() あなたの状態が達成されたかどうかテストする機能.この関数を何度も何度も呼び出す必要があります.この関数は共通の同期型であることに注意してください後でasync関数を考えます.
while (!condition()) {
  // nothing
}
これは確かに仕事ですが、それは他のすべてをブロックします.フロントエンドでは、ブラウザを完全に停止させる(そして、それに応答しないかもしれない).そしてバックエンドでは、他の何かを行うのを無効にする.すべての周りの悪いソリューション!

第二の解決策−睡眠とループ


連続した状態のテストが問題外であるので、我々はテストの間に若干の遅れを加えることができました.を使うpromise あなたのコードをいくつかの期間の間「スリープ」させる簡単な方法です.我々は、次の方法でそれを達成することができます.
const timeout = (time) => new Promise((resolve) => setTimeout(() => resolve(true), time));
あなたが呼ぶならばtimeout(1000) それは1000ミリ秒(1秒)後に真に解決するという約束を返します.そこで、次のようにコードを書くことができます.
  // test condition() every second
  while (!condition()) {     /* [1] */
    await timeout(1000);     /* [2] */
  }
  // now condition() is true
このループは条件[ 1 ]をテストします、そして、それが達成されないならば、それはもう一度[ 2 ]をテストする前に1秒待ちます.これはあなたのブラウザをブロックする可能性を無効にしたり、ノードを無効にするので、それは明確な勝利です.しかし、このコーディングパターンが何かであるので、我々は再び潜在的に必要とします.

第三の解決-約束と間隔


高次機能解決のために行きましょう:我々は、我々が待つことができる見込みを生じる機能を書きます.任意のループを記述する必要はありません.約束が解決されるとき、あなたの論理は進行します.我々の機能を呼びましょうuntil(...) 何故なら次のように書くことができるからです.
  // test condition() periodically
  await until(condition)
  // when the promise is resolved, condition() is true
このコードはよく読まれていることを認めなければなりません.実際には「状態まで待つ」ということです.どうやってこれをコード化できますか?約束を持つ代わりに、それ自体を解決するのに遅らせるためにタイムアウトを設定しましょうuse an interval (つまり、何かが定期的に行われます)、条件をテストし、条件がtrueのときにのみ約束を解決します.デフォルトでは試行の間に2番目を待ちましょう.
const until = (fn, time = 1000) =>
  new Promise((resolve) => {
    const timer = setInterval(() => {  /* [1] */
      if (fn()) {                      /* [2] */
        clearInterval(timer);          /* [3] */
        resolve(true);                 /* [4] */
      }
    }, time);
  });
我々until(...) 関数は2つのパラメータを持ちます.タイマーは、関数を繰り返しチェックする[ 1 ]を設定しますその関数が真[ 2 ]を返すとき、間隔はクリアされ、約束は解決される[ 4 ].今の状態を待って1つのライナー(以前に見たように)が修正されるいくつかの詳細があります.

第四の解決策なぜ待つか?


我々は、我々が望んだ解決策を達成しました、しかし、我々の実装は若干の問題を持ちます:状況がすでに満足したならば、どうですか?書かれているように、我々はテストまでの間隔を待ちます:時間の無駄.(私たちの2番目の解決策はこの問題を持っていませんでした.私たちは、すぐに条件をテストしたいです、そして、falseが間隔論理をする場合だけ.幸いにも、変化は難しくない.ただ最初にチェックしてください、そして、条件がすでに真実でないならば、間隔ループだけをしてください.
const until = (fn, time = 1000) => {
  if (fn()) {                              /* [1] */
    return Promise.resolve(true);          /* [2] */
  } else {                                 /* [3] */
    return new Promise((resolve) => {
      const timer = setInterval(() => {
        if (fn()) {
          clearInterval(timer);
          resolve(true);
        }
      }, time);
    });
  }
};
我々は最初のテスト[1]を加えました、そして、条件がすでに満たされるならば、我々は本当の[2]に解決される約束を返します.それ以外の[ 3 ]我々はちょうど前のバージョンのuntil(...) 間隔、テストなどで、これはより良い、より速い解決です、しかし、まだ扱われる何かがあります..エラーは?

第五の解決策-クラッシュについてはどうですか?


前の解決策は十分です.しかし、テスト機能がクラッシュしたらどうなりますか?この場合、約束は拒絶されなければならないので、状況は見つけられて、扱われることができます.我々はいくつかを追加する必要がありますtry...catch 問題に対処するブロック.
const until = (fn, time = 1000) => {
  try {                                 /* [1] */
    if (fn()) {
      return Promise.resolve(true);
    } else {
      return new Promise((resolve, reject) => {
        const timer = setInterval(() => {
          try {                         /* [2] */ 
            if (fn()) {
              clearInterval(timer);
              resolve(true);
            }
          } catch (e) {                 /* [3] */
            clearInterval(timer);       /* [4] */
            reject(e);                  /* [5] */
          }
        }, time);
      });
    }
  } catch (e) {                         /* [6] */
    return Promise.reject(e);           /* [7] */
  }
};
第一try...catch ブロック[ 1 ]は、最初のテストに必要です衝突するならば、我々はエラーオブジェクト[ 7 ]で拒絶された約束を返します.また、2番目の必要try...catch 内部ループのブロック[ 2 ]衝突[ 3 ]で、我々は間隔タイマ[ 4 ]をクリアしなければならなくて、それからエラーオブジェクトで約束[ 5 ]を拒絶しなければなりません.
この新しいロジックでテストは次のようになります.
  // test condition periodically
  try {
    await until(condition)
    // success: the condition was true
  } catch (e) {
    // failure: the test crashed
  }
(もちろん、条件をテストしても例外をスローすることはできませんtry...catch -- しかし、それはとにかく良い習慣です)
我々はほとんど完了です.しかし、まだヒッチです!

オープンソースセッション


Webアプリケーションの生産におけるデバッグは、挑戦的で、時間がかかるかもしれません.OpenReplay フルストーリー、ログオンとhotjarにオープンソースの代替手段です.それはあなたが監視し、すべてのユーザーが再生し、どのようにすべての問題のためにあなたのアプリの行動を示す再生することができます.
それはあなたのユーザーの肩を見ながら、ブラウザの検査官を開いているようなものだ.
OpenReplayは現在利用可能なオープンソースの代替手段です.

ハッピーデバッギング、現代のフロントエンドチームStart monitoring your web app for free .

第6回(最終回)解決


我々の以前の解決策はほとんど完璧ですが、条件が満たされない場合はどうですか?それは問題です:あなたのコードは無限ループになります.番目のパラメータを追加し、最大待ち時間を指定します.その時、条件が成立しなければ、何か問題があったとしましょう.
必要な変更は短く、幸いにも:私たちが最大の待ち時間を超えた場合、時間を維持し、見ての問題.最大待ち時間を定義するために余分なパラメータを追加します.
const until = (fn, time = 1000, wait = 10000) => {
  const startTime = new Date().getTime();            /* [1] */
  try {
    if (fn()) {
      return Promise.resolve(true);
    } else {
      return new Promise((resolve, reject) => {
        const timer = setInterval(() => {
          try {
            if (fn()) {
              clearInterval(timer);
              resolve(true);
            } else if (new Date().getTime() - startTime > wait) {
              clearInterval(timer);                  /* [2] */
              reject(new Error('Max wait reached')); /* [3] */
            }
          } catch (e) {
            clearInterval(timer);
            reject(e);
          }
        }, time);
      });
    }
  } catch (e) {
    return Promise.reject(e);
  }
};
必要な変更は小さいです:間隔を設定しなければならない場合には開始時刻[ 1 ]を保存します.ループでは、条件をテストした後、それが達成されていない場合は、十分な時間が経過した我々は間隔[ 2 ]をクリアし、約束を拒否する[ 3 ].今、我々はすべての可能性をカバーしてきた!

非同期バージョン


前のコードでは、条件テスト機能が同期しているという仮定のもとで働いていました.我々は非常に簡単に我々のコードを適応させることができます.
const untilAsync = async (fn, time = 1000, wait = 10000) => {
  const startTime = new Date().getTime();  /* [1] */
  for (;;) {                               /* [2] */
    try {
      if (await fn()) {                    /* [3] */
        return true;
      }
    } catch (e) {                          /* [4] */
      throw e;
    }

    if (new Date().getTime() - startTime > wait) {
      throw new Error('Max wait reached'); /* [5] */
    } else {                               /* [6] */
      await new Promise((resolve) => setTimeout(resolve, time));
    }
  }
};
テストが長すぎる場合には出発時刻を保存します.テスト関数が真[ 3 ]に解決するか、例外をスローするならば、我々は無限ループ[ 2 ]を終了するでしょう[ 4 ].関数が他の何かに解決して、真の値を解決し、エラーをスローしない場合は、十分な時間が経過したかどうかを確認しますもしそうなら、タイムアウト例外[ 5 ]をスローします.さもなければ、時間[ 6 ]がまだあるので、我々は若干の時間を待ちます、そして、もう一度テストしてください.
ここで重要な違いは、我々が使用する必要はないということですsetInterval(...) なぜなら、私たちはasync関数にあるので、直接await 必要な時間.しかし、両方のバージョンの関数を比較すると、パラレルは明確です.
  • ストアの開始時間無限の回避を避けるために
  • テストがtrueの場合、成功する
  • テストが例外をスローした場合、失敗します
  • を返します.
  • もう一度テストする前に
  • 完了するまでは、間隔または共通ループを介してこれを行う
  • 今、私たちは、任意の条件をチェックする必要がある2つのバージョンがあります-あなたが使用するいずれかを選択する必要があります.

    概要


    この記事では、一見些細な問題を解決する方法を見てきました.何かを待っています.ブラウザやサーバで問題を起こしない方法では、FPのテクニックを適用することで、高次の機能と約束をします.あなたも、あなたはそれを知っていることなく、FPを使用していることを見つけることができます!