nodejsの中の非同期プログラミングを深く理解します.


概要
Javascriptはデフォルトでは単一スレッドであるため、コードは新しいスレッドを作成して並列に実行できないことを意味する.しかし、ブラウザで最初に実行されたjavascriptにとって、単一スレッドの同期実行環境は明らかにページクリック、マウス移動などの応答ユーザの機能を満たしていない.このように、ブラウザはAPIのセットを実現し、javascriptがページの要求イベントに応答するようにリカバリすることができる.
さらに、nodejsは、非ブロッキングI/Oを導入し、非同期の概念をファイルアクセス、ネットワーク呼び出しなどに拡張する.
今日は、様々な非同期プログラムの長所と短所と発展傾向を深く検討します.
同期非同期とブロック非ブロック
nodejsの非同期プログラミングを議論する前に、私達に比較的に混淆しやすい概念を討論させてください.それは同期、非同期、閉塞、非閉塞です.
ブロッキングとブロッキングとは、プロセスやスレッドが操作やデータの読み書きをする時、待つ必要があるかどうか、待つ間に他の操作ができますか?
待ち時間が必要であり、待ち時間中にスレッドやプロセスが他の操作ができなくなり、馬鹿に待つしかないなら、この操作はブロックされていると言います.
逆に、プロセスまたはスレッドが操作またはデータの読み書き中に他の操作が可能であれば、この動作はブロックされていないということになる.
同期と非同期とは、データにアクセスする方法であり、同期とは、データを積極的に読み取る必要があることを指し、この読み取りプロセスは、ブロックされているか、またはブロックされていないかもしれない.非同期とは、積極的にデータを読み込む必要がなく、受動的な通知である.
明らかに、javascriptの中のコールバックは受動的な通知であり、我々は非同期呼出しと呼ぶことができる.
javascriptの中のコールバック
javascriptにおけるコールバックは非同期プログラミングの非常に典型的な例である.
document.getElementById('button').addEventListener('click', () => {
  console.log('button clicked!');
})
上記のコードの中に、私達はbuttonのためにclickイベントモニターを追加しました.もしclickイベントを傍受したら、コールバック関数を出発して、相応の情報を出力します.
コールバック関数は通常の関数ですが、パラメータとしてaddEvent Listenerに渡され、イベントがトリガされる時だけ呼び出しられます.
前の記事で述べたsetTimeoutとset Intervalは、実際には非同期のコールバック関数です.
コールバック関数のエラー処理
nodejsでは、コールバックのエラー情報はどう処理されますか?nodejsは非常に巧妙な方法を採用しています.nodejsでは、任意のコールバック関数の中の最初のパラメータはエラーオブジェクトです.このエラーオブジェクトの存在を判断することで、対応するエラー処理を行うことができます.
fs.readFile('/  .json', (err, data) => {
  if (err !== null) {
    //    
    console.log(err)
    return
  }

  //    ,     。
  console.log(data)
})
地獄のリベンジ
javascriptは非常に優れていますが、同期処理の問題を効果的に解決しました.しかし、残念なことに、私たちはコールバック関数の戻り値に依存して次のステップの操作を行う必要がある場合、このコールバック地獄に陥ることになります.
コールバック地獄というのはちょっと大げさですが、コールバック関数の問題を反映しています.
fs.readFile('/a.json', (err, data) => {
  if (err !== null) {
    fs.readFile('/b.json',(err,data) =>{
        //callback inside callback
    })
  }
})
どう解決しますか
ES 6がPromiseを導入することを恐れず、ES 2017はAync/Awaitを導入してもこの問題を解決できます.
ES 6のPromise
Promiseとは
Promiseは非同期プログラミングの解決法であり、従来の解決法「コールバック関数とイベント」より合理的で強力である.
Promiseとは、簡単に言えば、ある未来が終わるイベント(通常は非同期操作)の結果が格納されています.
文法的には、Promiseはオブジェクトであり、非同期操作のメッセージを取得することができます.
Promiseの特徴
Promiseには二つの特徴があります.
  • オブジェクトの状態は外部の影響を受けない.
  • Promiseオブジェクトは非同期操作を表しています.Pending(進行中)、Resoloved(完了しました.Fulfilledとも言います.)とRejeced(失敗しました.)の3つの状態があります.
    非同期操作の結果のみ、現在の状態はどの状態かを決定できます.他の操作はこの状態を変えることができません.
  • いったん状態が変化すれば、この結果はいつでも得られます.
  • Promiseオブジェクトの状態が変わるのは、PendingからReolvedに変化することとPendingからRejectiedに変化することしかできません.
    イベントとは全く違って、イベントの特徴は、それを見逃してしまったら、また傍聴しても結果が得られないということです.
    Promiseのメリット
    Promiseは非同期動作の流れを表現し、階層ネスティングのコールバック関数を回避した.
    Promiseオブジェクトは、非同期動作をより容易に制御するための統一インターフェースを提供する.
    Promiseの欠点
  • Promiseをキャンセルすることができません.新築したらすぐ実行します.途中でキャンセルできません.
  • コールバック関数が設定されていない場合、Promise内部で投げられたエラーは外部に反応しません.
  • Pending状態にあると、どの段階に進行しているかは分かりません.
  • Promiseの使い方
    Promiseオブジェクトは、Promiseのインスタンスを生成するためのコンストラクターです.
    var promise = new Promise(function(resolve, reject) { 
    // ... some code 
    if (/*        */){ 
    resolve(value); 
    } else { reject(error); } 
    }
    );
    promiseはthenに接続して操作できます.thenは2つのfunctionパラメータを操作します.最初のfunctionのパラメータはPromiseを構築する時のresove valueです.2番目のfunctionのパラメータはPromiseのrejectを構築するerrorです.
    promise.then(function(value) { 
    // success 
    }, function(error) { 
    // failure }
    );
    具体的な例を見ます.
    function timeout(ms){
        return new Promise(((resolve, reject) => {
            setTimeout(resolve,ms,'done');
        }))
    }
    
    timeout(100).then(value => console.log(value));
    PromiseではsetTimeout法を呼び出し、タイミングによってresove法をトリガし、パラメータdoneに入る.
    最後にプログラム出力done.
    Promiseの実行順
    Promiseは作成されるとすぐに実行されます.しかし、Promise.thenの方法は、コールサイクルが終わったら再度呼び出します.次の例を見ます.
    let promise = new Promise(((resolve, reject) => {
        console.log('Step1');
        resolve();
    }));
    
    promise.then(() => {
        console.log('Step3');
    });
    
    console.log('Step2');
    
      :
    Step1
    Step2
    Step3
    asyncとawait
    Promiseはもちろんいいです.私たちは転調地獄をチェーン式に変えました.私たちはthenを用いて複数のPromiseを接続し、前のpromise resoveの結果は次のpromiseにおけるthenのパラメータです.
    チェーンコールにはどんな欠点がありますか?
    例えば、私たちは一つのpromiseの中から、resoveは一つの価値を持っています.この値に基づいていくつかの業務ロジックの処理を行う必要があります.
    この業務ロジックが長いと、次のthenに長い業務ロジックコードを書く必要があります.このように私達のコードを非常に冗長に見せます.
    何か直接promiseの中でresoliveに戻る方法がありますか?
    答えはawaitです.
    promiseの前にawaitを加えると、呼び出されたコードはpromiseが解決されるか拒否されるまで停止します.
    awaitは必ずasync関数に入れます.asyncとawaitの例を見てみます.
    const logAsync = () => {
      return new Promise(resolve => {
        setTimeout(() => resolve('   '), 5000)
      })
    }
    上記のlogAsync関数を定義しました.この関数はPromiseに戻ります.このPromiseの内部にsetTimeoutを使ってresoliveに来たので、非同期と見なしてもいいです.
    awaitを使ってresoliveの値を得るなら、asyncの関数に置く必要があります.
    const doSomething = async () => {
      const resolveValue = await logAsync();
      console.log(resolveValue);
    }
    asyncの実行順
    awaitは実際にpromiseのresoveの結果を待っています.上記の例を組み合わせます.
    const logAsync = () => {
        return new Promise(resolve => {
            setTimeout(() => resolve('   '), 1000)
        })
    }
    
    const doSomething = async () => {
        const resolveValue = await logAsync();
        console.log(resolveValue);
    }
    
    console.log('before')
    doSomething();
    console.log('after')
    上記の例出力:
    before
    after
       
    aysncは非同期的に実行され、その順序は現在のこの周期の後であることが分かる.
    asyncの特徴
    asyncは、すべての後に続く関数をPromiseに変えます.後の関数が表示されていなくてもPromiseに戻ります.
    const asyncReturn = async () => {
        return 'async return'
    }
    
    asyncReturn().then(console.log)
    Promiseだけが後にthenに接続できるので、asyncは普通の関数をPromiseにパッケージ化していることが分かります.
    const asyncReturn = async () => {
        return Promise.resolve('async return')
    }
    
    asyncReturn().then(console.log)
    締め括りをつける
    プロミスは地獄の転調を避け、calback inside calbackをthenのチェーンコール形式に書き換えました.
    しかし、チェーンの呼び出しは読みやデバッグに不便です.そこでasyncとawaitが現れました.
    asyncとawaitはチェーンコールを似た手順で実行する文法に変えて、より分かりやすく調整します.
    比較を見てみます.まずPromiseを使う状況を見てみます.
    const getUserInfo = () => {
      return fetch('/users.json') //       
        .then(response => response.json()) //    JSON
        .then(users => users[0]) //        
        .then(user => fetch(`/users/${user.name}`)) //       
        .then(userResponse => userResponse.json()) //    JSON
    }
    
    getUserInfo()
    それをasyncとawaitに書き換える:
    const getUserInfo = async () => {
      const response = await fetch('/users.json') //       
      const users = await response.json() //    JSON
      const user = users[0] //        
      const userResponse = await fetch(`/users/${user.name}`) //       
      const userData = await userResponse.json() //    JSON
      return userData
    }
    
    getUserInfo()
    業務ロジックがより明確になっているのが見えます.同時に、私達は多くの中間値を取得しました.これも調整に便利です.
    本文の作者:flydeanプログラムのあれらの事
    本論文のリンク:http://www.flydean.com/nodejs-async/
    flydeanのブログ
    私の公衆番号に注目してください.「プログラムに関すること」を最も分かりやすく解読し、最も深い商品、最も簡潔な教程、多くのあなたの知らない小さな技術などを発見してください.