Promiseの実装から見た有限状態機

9633 ワード

前に書く
有限状態機は私が大学院に通っている間に必修の課程で、つまり大部分のCS大学院生が接触しなければならない課程です.この授業は簡単と言っても簡単ですが、その中に含まれている内容や応用が多すぎます.
なぜこんなに簡単なものを複雑に見える数学モデルで表現するのかという人もいます.私たちが普段接触している多くのプログラミングに関する知識はこの数学モデルから発展したからです.あなた自身が知らないうちに、この理論を使ってcodingしたのかもしれません.数学の抽象は人にもっと系統的にこの理論を理解させることができて、そして導きます.
言うことは味がない.フロントエンドのいくつかのものから有限状態機の影が見えます.では、いくつかの実際の応用から始めて、有限状態機を理解します.
このシリーズはいくつかの文章に分かれているかもしれませんが、臭いし、長いので、簡単から抽象までです.
ゆうげんじょうたいき
まず、有限状態機について簡単に説明します.百科事典の説明は簡単です.
有限状態機械(英語:finite-state machine:FSM)は有限状態自動機とも呼ばれ、状態機械と略称され、有限状態およびこれらの状態間の遷移や動作などの挙動を表す数学モデルである.wikiから
有限ステートマシンという名前の核心は2つの言葉です:有限とステータス(くだらないことを言ったようです..).
有限はここで定语で、状态が有限であることを表して、つまり1つの正しい有限な状态の机は有限な状态だけあって、しかしこの状态の机が绝えず运行することを许します.
最も簡単で最もよく使われる有限状態機の例は信号で、3つの色は3つの異なる状態を代表して、一定の条件(ここでは遅延)によって、状態の転換をトリガします.
1つのステータスマシンを構成する最も簡単な要素(ここでは信号を例に挙げます):
  • 状態セット:赤、黄、緑;
  • 初期状態:信号の起動状態;
  • 状態遷移関数:遅延または動的スケジューリング;
  • 最終状態:信号がオフの状態は、もちろん存在しない可能性があります.

  • こんなにたくさん言ったのは、数学のモデルです.では、フロントエンドの分野で簡単な有限状態機はありませんか.もちろんたくさんありますが、この記事ではまずPromiseとステータスマシンの関係についてお話しします.
    Promiseの実装
    直接Promiseとステータスマシンの関係はそんなによく理解できないかもしれませんが、ここでは結論を残して、簡単なPromiseの実装コードを見て、有限ステータスマシンのPromiseでの機能を大まかに理解することができます.
    Promiseは4つのステータスを持つ有限ステータスマシンで、3つのコアステータスはPending、Fulfilled、Rejectedであり、それぞれこのPromiseが停止、完了、拒否されていることを示しています.Promiseがまだ実行されていないことを示す追加の初期状態もあります.この状態は厳密にはPromiseの状態ではありませんが、実際のPromiseの使用中には、この状態が備えられています.
    以上の説明によれば,この有限状態機のフレームワークが大まかに構築されている.
    では、有限状態機の構成要素から、Promiseを自分で実現することができます.
    Promise
    PromiseはES 6規格で提供される非同期動作の優れた構文糖であり,元のコールバック関数モードをカプセル化し,非同期動作に対するチェーン呼び出しを実現した.さらにgeneratorやasync構文糖を配合して使用すると便利です.
    Promiseは現在、多くのブラウザでサポートされていますが、Promiseを見ていると、Promiseの多くの場所についてまだよく知られていないことがわかります.その内部の実装メカニズムも含め,このコードを書く目的はPromiseの使用をよりよく理解することである.
    Promiseの具体的な使い方は私のこのブログをご覧ください.ここではPromiseオブジェクト自体の使い方について説明しません.デフォルトでは基本的なPromiseの使い方を把握しています.よくわからない場合は、Promisegeneratorasync/awaitをご覧ください.
    次の具体的なコードは、私のgithubのfake-promiseを参照してください.
    初期状態:new Promiseまず,ES 6オリジナルのPromiseオブジェクトにとって,初期化の過程でパラメータとしてfunction(resolve, reject){}関数を渡し,この関数は非同期動作のために用いられた.
    現在javascriptの大部分の非同期操作はcallbackの方式で行われており、Promiseのコールバックは1つの関数に伝達され、この関数の受信2つのパラメータは、それぞれ状態がFULFILLEDおよびREJECTEDに遷移したときに呼び出される.
    非同期操作が失敗した場合、失敗原因を処理した後、reject(err)関数を呼び出すのは当然である.
    var p = new Promise(function(resolve, reject) {
      fs.readFile('./readme', function(err, data) {
        if (err) {
          reject(err);
        } else {
          resolve(data);
        }
      });
    });
    

    すなわち,この2つのパラメータ関数はいずれも非同期操作が完了した後に呼び出される.
    この点については、まずPromiseのコンストラクション関数(これはPromise-polyfillの一般的なフレームワークと初期化関数です):
    const promiseStatusSymbol = Symbol('PromiseStatus');
    const promiseValueSymbol = Symbol('PromiseValue');
    const STATUS = {
      PENDING: 'PENDING',
      FULFILLED: 'FULFILLED',
      REJECTED: 'REJECTED'
    };
    const transition = function(status) {
      var self = this;
      return function (value) {
        this[promiseStatusSymbol] = status;
        this[promiseValueSymbol] = value;
      }
    }
    const FPromise = function(resolver) {
      if (typeof resolver !== 'function') {
        throw new TypeError('parameter 1 must be a function');
      }
      this[promiseStatusSymbol] = STATUS.PENDING;
      this[promiseValueSymbol] = [];
      this.deps = {};
      resolver(
        //         ,        resolver reject。
        //             Promise         
        transition.call(this, STATUS.FULFILLED),
        transition.call(this, STATUS.REJECTED)
      );
    }
    
    new Promiseの初期化が行われた後、このPromiseは自分の最初の状態、すなわち初期状態に入った.
    ステータスセット:PENDINGFULFILLEDREJECTEDここのFULFILLEDの状態は実はResolvedで、ただResolvedという単語はあまりにも困惑しているだけで、FULFILLEDはもっとこの状態の意味を体現することができます.
    Promiseを使用した経験によれば、ライフサイクル全体がステータスであるべきであり、非同期操作が開始されたが、まだ結果が出ていない場合は、ステータスPENDINGが保留され、その後、成功と失敗のステータスであるべきである.
    コンストラクション関数に転送される関数は、非同期操作を開始するためにコンストラクション関数で呼び出される必要があります.次に、成功と失敗の状態と値をそれぞれ変更するために、渡された2つの関数を使用します.Promiseにカプセル化された関数を呼び出すと、このステータスマシンが起動します.起動後、この非同期動作が10 S実行されると仮定すると、ステータスマシンは、実行後、StartからPENDINGに変更され、この非同期動作が停止されたことを示す.
    10秒のPENDING状態は、非同期操作が完了した後、2つのブランチが存在します.
  • この非同期操作が成功し、エラーが投げ出されなければ、ステータスマシンはFULFILLEDにジャンプする.
  • 非同期動作が失敗したり、エラーが投げ出されたりした場合、ステータスマシンはREJECTEDにジャンプします.

  • 上記のプロセス全体はPromiseステータスマシンの最も根本的なプロセスであるが、Promiseはチェーン呼び出しを行うことができ、つまりこのステータスマシンは状態の変化を繰り返し行うことができる.
    FPromise.prototype.then = function(onFulfilled, onRejected) {
      const self = this;
      return FPromise(function(resolve, reject) {
        const callback = function() {
          //     ,              ,       ,
          //          ,                 
          // resolve  
          const resolveValue = onFulfilled(self[promiseValueSymbol]);
          resolve(resolveValue);
        }
        const errCallback = function() {
          const rejectValue = onRejected(self[promiseValueSymbol]);
          reject(rejectValue);
        }
        //       Promise     ,     Promise   then       
        //    ,     Promise           
        if (self[promiseStatusSymbol] === STATUS.FULFILLED) {
          return callback();
        } else if (self[promiseStatusSymbol] === STATUS.REJECTED) {
          return errCallback();
        } else if (self[promiseStatusSymbol] === STATUS.PENDING) {
          self.deps.resolver = callback;
          self.deps.rejecter = errCallback;
        }
      })
    }
    
    thenメソッドは、Promiseがチェーン呼び出しを行う根本であるべきである.
    まず,then法は成功と失敗のコールバックの2つのパラメータを有し,
    次に、チェーンの次のノードを呼び出すために新しいPromiseオブジェクトを返すべきである.
    最後に、ここで自身のPromiseオブジェクトの状態がFULFILLEDまたはREJECTEDであれば、コールバック関数を直接呼び出すことができ、そうでなければ非同期操作の完了状態が発生するのを待つ必要がある.
    ≪ステータス移行関数|Status Transfer Function|ldap≫:チェーン・コール
    厳密には、状態の遷移を行うたびに、現在の非同期動作の実行状態に基づいて判断される.しかし、非同期操作の反復はPromiseのチェーン操作に依存します.そうしないと、このステータスマシンはこのような多くのステータス遷移プロセスを生成しません.
    チェーン呼び出しの根本は収集に依存しており,一般的にPromiseのコードは非同期であり,関数を実行している場合にすぐにコールバック内の関数を実行することは不可能である.
    if (self[promiseStatusSymbol] === STATUS.FULFILLED) {
    	return callback();
    } else if (self[promiseStatusSymbol] === STATUS.REJECTED) {
    	return errCallback();
    } else if (self[promiseStatusSymbol] === STATUS.PENDING) {
    	//     PENDING  ,      
    	self.deps.resolver = callback;
    	self.deps.rejecter = errCallback;
    }
    

    依存性が収集された後,状態が変化したとき,setterを用いて状態の変化に応答し,対応するコールバックを実行した.
    const transition = function(status) {
      return (value) => {
        this[promiseValueSymbol] = value;
        setStatus.call(this, status);
      }
    }
    /** 
      *            ,         。
      *       PENDING --> FULFILLED,         onFulfilled  
      *       PENDING --> REJECTED,          onRejected  
      *
      * @returns void
      */
    const setStatus = function(status) {
      this[promiseStatusSymbol] = status;
      if (status === STATUS.FULFILLED) {
        this.deps.resolver && this.deps.resolver();
      } else if (status === STATUS.REJECTED) {
        this.deps.rejecter && this.deps.rejecter();
      }
    }
    

    最初の非同期実行が完了すると、その依存のresolverまたはrejecterが実行される.その後、このresolverに新しいPromiseを返すと、この新しいPromise p 2はp 1に続いて実行を開始することができ、p 2の構造はp 1とそっくりであり、それが構築された後、同様に依存収集およびチェーン呼び出しを行い、状態が複数回循環する有限状態機を形成する.
    有限状態機とPromiseここまで来ると、有限状態機とPromiseの関係が見られるはずです.実はPromiseは収集過程に依存する以外に、信号のような有限状態機です.Promiseは、実質的に有限状態機械のすべての主要な要因を有する.Promiseのステータスマシンは、そのライフサイクル中にステータスの移行によって非同期関数の同期実行を制御し、コールバック関数のcallback hellをある程度保証する.Promiseという比較的簡単なものに加えて,有限状態機械数学モデルの実現を採用したほか,フロントエンドには状態機械に関する他の実践がある.そして非常に複雑な実践もあります.次はReduxの実装とオートマチックとの関係についてお話しします(このビジネス反復サイクルで書く時間があるかどうかはわかりませんが...)