JS-Generatorアクチュエータの実現

20577 ワード

Generatorは非同期的に作動する容器である.その自動実行には非同期操作が結果を得たときに自動的に実行権が渡される仕組みが必要です.
2つの方法は、アクチュエータを実現することができる.
  • コールバック関数.非同期の操作をThunk関数に包装して、コールバック関数の中で実行権を返します.
  • Promiseオブジェクトです.非同期操作をPromiseオブジェクトにパッケージし、then方法で実行権を付与する
  • .
    Thunk関数がgeneratorアクチュエータを実現します.
    jsで非同期を実現する方法は、ファイルを読み込む操作などのコールバック関数です.
    fs.readFile(path, function callback(err, data) {
      console.log(data);
    });
    
    上記のfs.readFileには二つのパラメータがあり、第一のパスパスパスパスパスパスパスパスパスパスパスパスパスパスパスパス、第二はコールバック関数である.readFile関数を単一パラメータの関数に変更すると、関数コリック化に似ています.
    function thunkFile(path) {
      return function(cb) {
        return fs.readFile(path, cb)
      }
    }
    
    変換を経て、単一パラメータの関数として、毎回一つのパラメータだけを受け取ります.thunkFile関数を呼び出してファイルを読みます.
    let thunk = thunkFile('./a.txt');
    thunk(function(err, data) {
      if(err) return;
      console.log(data);
    });
    
    thunkはいわゆるthunk関数です.いわゆるthunk関数とは、パラメータの関数として1つのコールバック関数を受けることです.
    任意のコールバック関数はパラメータの関数であり、thunk関数として作成できます.以下は簡単なThunk関数変換器です.
    // es5  
    function Thunk(fn) {
      return function() {
        let args = [...arguments];
        return function(cb) {
          args.push(cb);
          return fn.apply(this, args);
        };
      }
    }
    
    GEneratorが非同期操作を実現するには、yieldの後に非同期動作の表現を接続することができますが、問題はどのようにして前のステップのyieldの実行が完了したら次のステップのyieldを実行することができますか?
    function* gen() {
      let result1 = yield readFile('./a.txt');
      let result2 = yield readFile(`${result1}.txt`);
      console.log(result1);
      console.log(result2);
    }
    
    var g = gen();
    var res = g.next();
    
    while(!res.done){
      console.log(res.value);
      res = g.next();
    }
    
    上のコードでは、Generator関数genが自動的にすべてのステップを実行します.しかし、これは非同期操作には適していない.reult 1はまだ帰っていないので、第二のyieldはすでに実行されているかもしれません.第二のyieldを第一のyieldの結果を得てから実行しなければならない.これは第一のyieldのコールバック関数に関数の実行権を返納しなければならない.すなわちg.next()を実行してこそ、第一のyieldが結果を得てから第二のyieldを実行することができる.
    この考えに沿って、上のgenerator関数を手動で実行します.
    let g = gen();
    let info1 = g.next();
    info1.value(function(err, data) {
      if(err) throw err;
      let info2 = g.next(data);
      info2.value(function(err, data) {
        if(err) throw err;
        g.next(data)
      });
    })
    
    上のコードをよく見ると、Generator関数の実行過程は、実は同じコールバック関数を、nextメソッドのvalue属性に繰り返し導入していることが分かります.これは再帰的にこのプロセスを自動的に完成させることができるようにします.
    ここでは、自分で一般的なThunk関数に基づくgeneratorアクチュエータを書くことができます.
    function run(genFn) {
      let gen = genFn();
      function next(err, data) {
        let result = gen.next();
        if(result.done) return result.value;
        result.value(next);
      }
      next();
    }
    
    上のコードのrun関数は、Generator関数の自動実行器です.内部のnext関数はThunkのコールバック関数です.next関数は、まず、ポインタをGenerator関数の次のステップに移動し(gen.next方法)、その後、Generator関数が終了したかどうかを判断し(result.done属性)、終了していない場合は、next関数を再度Thunk関数に伝え(result.value属性)、そうでない場合はそのまま終了します.
    このアクチュエータがあって、Generator関数を実行するのが便利になりました.内部にいくつかの非同期操作があっても、直接Generator関数をrun関数に導入すればいいです.もちろん、前提は各非同期の操作であり、Thunk関数なら、すなわち、yield命令の後に付いているのはThunk関数である必要があります.
    var g = function* (){
      var f1 = yield readFileThunk('fileA');
      var f2 = yield readFileThunk('fileB');
      // ...
      var fn = yield readFileThunk('fileN');
    };
    
    run(g);
    
    Thunk関数はgenerator関数の自己実行を実現する唯一の方法ではなく、自己実行の鍵は、GEnerator関数の流れを自動的に制御する仕組みが必要であり、受信と交換関数の実行権が必要である.コールバック関数はこの点ができます.Promiseもいいです.
    Promise実現generatorアクチュエータ
    Promiseがgeneratorアクチュエータを実現する原理は、非同期操作をPromiseオブジェクトにパッケージし、thenメソッドで実行権を返納することです.
    例えば、ファイル読み込みは非同期I/O操作であり、読み取り操作をまずプロミセオブジェクトにパッケージし、その後、thenを使用して実行権を取得する.
    let fs = require('fs');
    function readFile(path) {
      return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
          if(err) return reject(err);
          resolve(data); 
        });
      });
    }
    
    function* genReadFile() {
      let f1 = yield readFile('./a.txt');
      let f2 = yield readFile('./b.txt');
    }
    
    プロミセに基づくプロミセのジェネナート自己実行器です.
    function run(genFn) {
      let gen = genFn();
    
      function next(data) {
        let result = gen.next(data);
        result.value.then(_data => {
          next(_data);
        });
      }
    
      next();
    }
    
    //      
    run(genReadFile);
    
    締め括りをつける
    非同期generatorアクチュエータを実現するための鍵は、前のyieldが結果を返した後、ジェネレータオブジェクトのnext()を呼び出して次のyieldを実行することです.非同期動作については、現在の非同期の実行が完了したということは、コールバック関数またはpromise.thenにおいてのみ保証され得るので、Thunk関数とpromiseオブジェクトの2つの方法に基づくgenerator実行器がある.
    ですから、generatorアクチュエータを使うには、generatorのyieldの後ろにThunk関数またはpromiseオブジェクトを接続しなければなりません.