koaの非同期を深く掘り下げて処理編を戻します.


前のページでは、コーナの中間部品の玉ねぎモデルの実行原理を整理し、玉ねぎモデルを自動的に走らせることができるプロセス管理関数を実現しました.この章では、私たちはもう一度コーナの中の非同期の書き方の原理を研究します.同じように、管理関数を実現します.はい、同期化された書き方で、非同期のコールバック関数を書くことができます.
1.ピラミッドと理想的な解決策を見直す.
私たちはjavascriptが単スレッド非閉塞言語であることを知っています.非同期非ブロッキングはもちろんその利点の一つですが、大量の非同期動作は必然的に大量のコールバック関数に関連しています.特に非同期ネスト時には、コードの読み取り可能性が非常に悪いピラミッドの問題が発生します.例えば次の例:
var fs = require('fs');

fs.readFile('./file1', function(err, data) {
  console.log(data.toString());
  fs.readFile('./file2', function(err, data) {
    console.log(data.toString());
  })
})
この例は、2つのファイルの内容を順次読み取ってプリントするもので、file 2の読み取りは、file 1の読み取りが終了した後に行われる必要があるため、file 1で読み込まれたコールバック関数で行われなければならない.これは典型的なレプリカの入れ子で、しかも二階しかないです.実際のプログラミングでは、より多くの層の入れ子に出会うかもしれません.このようなコードの書き方は決して優雅ではありません.
私達の想像の中で、比較的に優雅な書き方は同期のように見えるべきです.
var data;
data = readFile('./file1');
//         readFile           
console.log(data.toString());
//         readFile   
data = readFile('./file2');
console.log(data.toString());
このような書き方では、地獄に戻ることは全く避けられます.実は、koaはこのような書き方で、非同期コールバック関数を書くことができます.
var koa = require('koa');
var app = koa();
var request=require('some module');

app.use(function*() {
  var data = yield request('http://www.baidu.com');
  //         
  this.body = data.toString();
})

app.listen(3000);
では、一体何がkoaにこんな不思議な魔力を持たせるのですか?
2.generatorはpromiseに協力して、非同期の書き方を実現する.
肝心な点は、前編でも触れましたが、ゲナートは「ブレークポイント」のような効果を持っています.yieldに会ったら、一時停止します.コントロールをyieldの後ろの関数に渡して、また戻ったら実行します.
上のあのkoaの例では、yieldの後ろのはどんな対象でもいいですよ.特定のタイプでなければなりません.co関数では、プロミセ、thunk関数などがサポートされます.
今日の文章の中で、プロミスを例にして分析してみます.どのようにgeneratorとpromiseを使って、同期化を実現しますか?
最初の読取ファイル例でも解析します.まず、私たちはファイルを読む関数を改造して、それをプロミセオブジェクトにパッケージ化する必要があります.
var fs = require('fs');

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//   readFile     
var tmp = readFile('./file1');
tmp.then(function(data) {
  console.log(data.toString());
})
promiseの使用については、慣れていないなら、es 6の文法を見に行ってもいいです.(近いうちに、私も文章を書いて、皆さんにs 5の文法で自分で基本的な機能を持つプロミの対象を実現する方法を教えます.期待してください.
簡単に言えば、プロミスはプロミゼ.then(calback)という形でレス関数を書くことができます.しかし、私たちの目標はgeneratorに協力して、本当にシルクのように滑らかな同期化の書き方を実現することです.どう協力すればいいですか?このコードを見てください.
var fs = require('fs');

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//         generator 
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}

//    generator
var g = gen();
var another = g.next();
//another.value     promise  
another.value.then(function(data) {
  //    g.next      generator,  data      
  var another2 = g.next(data);
  another2.value.then(function(data) {
    g.next(data);
  })
})
上記のコードの中で、私達はgeneratorの中でyieldがreadFileを作りました.回調文コードはyieldの後のコードに書いています.完全に同期した書き方です.文章の最初の構想を実現しました.
私達が得たのはAnother.valueです.then文を使ってコールバック関数を定義できます.関数の内容は、読み取ったdataをgeneratorに戻して、引き続きgeneratorを断点から実行させます.
基本的にこれは非同期化の最も核心的な原理です.実際にpythonを知っていれば、pythonには「協働」という概念があり、基本的にはゲナートを使って実現されていることが分かります.
しかし、上記のコードは依然として手動で実行しています.前編と同じように、私達はまた一つのrun関数を実現して、generatorの流れを管理する必要があります.自動的に走ることができます.
3.同期化コールバック関数を自動的に実行させる:一つのrun関数の作成
前のコードの中で手動でgeneratorを実行する部分をよく観察しても、一つの法則を見つけることができます.この法則は再帰的な関数を直接書いて代替できます.
var run=function(gen){
  var g;
  if(typeof gen.next==='function'){
    g=gen;
  }else{
    g=gen();
  }

  function next(data){
    var tmp=g.next(data);
    if(tmp.done){
      return ;
    }else{
      tmp.value.then(next);
    }
  }

  next();
}
関数は一つのgeneratorを受信し、その中の非同期を自動的に実行することができます.このrun関数を使って、前の非同期コードを自動的に実行させます.
var fs = require('fs');

var run = function(gen) {
  var g;
  if (typeof gen.next === 'function') {
    g = gen;
  } else {
    g = gen();
  }

  function next(data) {
    var tmp = g.next(data);
    if (tmp.done) {
      return;
    } else {
      tmp.value.then(next);
    }
  }

  next();
}

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//         generator 
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}
//      gen  run        
run(gen);
上記のコードを実行すると、端末が順にfile 1とfile 2のコンテンツをプリントアウトしているのが見えます.
ここのrun関数は簡単のためにpromiseだけをサポートしていますが、実際のco関数はthunkなどもサポートしています.
このようにして、co関数の2つの機能は基本的に完全に紹介されています.一つはタマネギモデルのプロセス制御、もう一つは非同期コードの自動実行です.次の文章では、この二つの機能を統合して、自分達の一つのco関数を書き出します.
この文章のコードは同じで、githubの上に見つけられます.https://github.com/mly-zju/async-js-demoその中のプロモーションgenerator.jsは本編のソースコードの例です.
皆さん、私のgithub pages個人ブログに注目してください.不定期に私の技術記事を更新します.