JavaScript非同期プログラミング高速応答ネットワーク応用


JavaScriptは、マルチメディア、マルチタスク、マルチコアネットワークの世界における単一スレッド言語となっています.イベントモデルを用いて非同期トリガタスクを処理する行為は,JavaScriptを開発言語の利器として達成した.JavaScriptの非同期プログラミングをどのように深く理解し、把握するかが特に重要になります!!!「JavaScript非同期プログラミング設計高速応答ネットワークアプリケーション」は、いくつかの方法とインスピレーションを提供しています.
一、JavaScriptイベントを深く理解する
1.イベントのスケジュール
JavaScriptイベント・プロセッサは、オンライン・プログラムがアイドルになるまで実行されません(アイドル時に実行されます).
var start = new Date();
setTimeout(function() {
    var end = new Date();
    console.log('Time elapsed:', + (end - start), 'ms');
}, 500);
while(new Date - start < 1000) {};
//   :Time elapsed: 1001 ms(   1000)

settimeoutコールバックはwhileサイクルが終了するまでトリガーされないからです!settimeoutを呼び出すと、待ち時間イベントがキューに格納されます.次に、settimeoutコールバック関数(遅延時間に達した場合)を実行するまで、次の行のコードを実行します(プロセッサがアイドルになった場合).JavaScriptコードは、実行中にキューイベントをスケジュールするだけで済むため、コードの実行が終了するまでトリガーされません.参考:JavaScriptイベント駆動メカニズム&タイマメカニズム
2.非同期関数のタイプ
JavaScript非同期関数は、I/O関数(非ブロック)とタイミング関数の2つに分類されます.
/* test.js */
var obj = {};
console.log(obj);
obj.foo = 'bar';

WebKitのconsole.logは非同期行為を示したことで多くの開発者を驚かせた.ChromeまたはSafariでは、次のコードがコンソールに{foo:bar}と記録されます.WebKitのconsole.logはすぐにオブジェクトスナップショットを撮影するのではなく、逆にオブジェクトへの参照を1つだけ格納し、コードがイベントキューに戻ったときにスナップショットを撮影します.Nodeのconsole.logは別のことで、厳格に同期しているので、同じコードが出力されるのは{}注意:コンソールに{foo:bar}を記録し、先に実行した後にコンソールを開く!コンソールでコードをデバッグするときは、特に注意してください.
3.非同期関数の作成
関数(非同期関数)を呼び出すと、プログラムはその関数が返された後にのみ続行されます.この関数は、将来別の関数(コールバック関数)を実行することになります.JavaScriptは、非同期操作が終了する前に関数が戻るのを阻止するメカニズムを提供していません.一部の関数は、有用な値を返すとともに、コールバックも使用します.この場合、コールバックは同期呼び出し(戻り値の前)、非同期呼び出し(戻り値の後)のいずれかであることを忘れないでください.潜在的な同期を定義しないで、コールバックに使用できる関数(コールバックは戻り値に依存)を定義します.
function test(callback) {
    var obj = {
        sendData: function() {
            console.log(arguments);
        }
    };
    callback();     // setTimeout(callback, 0);     
    return obj;
}

var obj = test(function(){
    obj.sendData("test callback");      //            
});

関数が値を返し、コールバックを実行する場合は、コールバックが値を返した後に実行されることを確認します!!
4.非同期エラーの処理
try{
    setTimeout(function() {
        throw new Error("Catch me if you can!");
    }, 0);
} catch(e) {
    console.log(e);
}

try/catch文はsettimeout関数自体の内部で発生したエラーのみをキャプチャできます!したがって、コールバックに由来する非同期エラーは、コールバック内部でのみ処理できます.
setTimeout(function() {
    try{
        throw new Error("Catch me if you can!");
            } catch(e) {
        console.log(e);
    }
}, 0);

未取得例外の処理:(1)ブラウザ環境で
window.onerror = function(err) {
    return true;    //         
}

(2)ノード環境で
process.on('uncaughtException', function(err) {
    console.log(err);   //       
})

5.ネストコールバックのネスト解除
JavaScriptで最も一般的な逆モードの方法は、コールバック内部にコールバックをネストすることです.2つ以上の関数がネストされないようにしてください.重要なのは、非同期呼び出しの関数をアクティブにする外部に非同期結果を格納する方法を見つけることです.これにより、コールバック自体がネストされる必要はありません.
二、分布式イベント
事件の蝶は偶然羽を振って、応用全体が反応した.ここで説明する方法は、パブリッシュ/サブスクリプションモード、すなわちオブザーバモードである.私のブログで紹介したように、JavaScriptデザインモード–オブザーバーモード
1.ノードのEventEmitterオブジェクト
odeの多くのオブジェクトはイベントを配布します:net.サーバオブジェクトは、新しい接続があるたびにイベント、fsを配布します.readStreamオブジェクトは、ファイルが開いたときにイベントを発行します.これらのイベントを生成するオブジェクトはすべてeventsです.EventEmitterの例.require("events")でモジュールにアクセスできます.
//   EventEmitter 
var EventEmitter = require('events').EventEmitter;

var emitter = new EventEmitter();
//     
emitter.on("myCustomerEvent", function(message) {
    console.log(message);
});
//     
emitter.emit("myCustomerEvent", "ligang");

2.独自のイベント配信システムを実現
function MyEvents() {
    this._events = {};
    /** *      * @param names      * @param callback        * @param data           , callback   this.data      */
    this.on = function(names, callback,data) {
        //       ,        
        //       “  、  、  ”  
        var nameList = names.split(/[\,\s\;]/);
        var index = nameList.length;
        while (index) {
            index--;
            var name = nameList[index];
            if (!this._events[name]) {
                this._events[name] = [];
            }
            this._events[name].push({callback:callback,data:data});
        }
    };
    /** *      * @param name      * @param callback        */
    this.off = function(name, callback) {
        //         ,      
        if (!name) {
            this._events = {};
            return;
        }
        var event = this._events[name];
        //        ,    
        if (!event) {
            return;
        }
        //       ,     
        if (!callback) {
            delete this._events[name];
        } else {
            var length = event.length;
            while (length > 0) {
                length--;
                if (event[length].callback === callback) {
                    event.splice(length, 1);
                }
            }
        }
    };
    /** *      * Eg:A.B.C *     :A.B.C ==> A.B ==> A * @param name      * @param args    */
    this.emit = function(name, args) {
        var handleEvent = name,
            namesAry = handleEvent.split(".");
        for(var i = 0, len = namesAry.length; i < len; i++) {
            var event = this._events[handleEvent];
            if (event) {
                var j = 0, length = event.length;
                while (j < length) {
                    event[j].callback(args);
                    j++;
                }
            }
            namesAry.pop();
            handleEvent = namesAry.join(".");
        }
    };  
}

3.同期性
$("input[type='submit']")
    .on("click", function(){
       console.log("click");
    }).trigger("click");    //     
console.log("lalala");
//      :click lalala

これはclickイベントの処理関数がtrigger法により直ちに活性化されることを実証した.実際,jQueryイベントがトリガーされると,そのすべてのイベント処理関数が中断されることなく順次実行される.ユーザーがsubmitボタンをクリックすると、これは確かに非同期イベントです!!!
4.jQueryカスタムイベント
カスタムイベントは、jQueryが過小評価されている機能の1つであり、強力な分散イベントシステムのWebアプリケーションへの移植を簡素化し、追加のライブラリを必要としません.泡が立つのはあるDOM元素がある事件を触発したのでさえすれば、その親元素は続いてこの事件を触発して、続いて親元素の親元素で、このようにして、ずっとルート元素documentまで遡ります;このバブルの道のどこかでイベントのstopPropagationメソッドが呼び出されない限り(イベント処理関数がfalseを返すとjQueryがstopPropagationメソッドを自動的に呼び出す).blur、focus、mouseenter、mouseleaveはバブルをサポートしていないことに注意してください.例:jQueryカスタムイベントもバブルをサポート
$(".pt-login-logo-signin, document").on("fizz", function(){
   console.log("fizz");
}).trigger("fizz");

泡を立てたくない場合がありますが、幸いなことにjQueryは対応する方法triggerHandler()を提供しています.この特別な方法は、指定されたイベントタイプ上のすべてのバインドされた処理関数をトリガーします.ただし、ブラウザのデフォルトの動作は実行されず、イベントバブルも発生しません.この方法の動作はtriggerと似ていますが、*第一に、ブラウザのデフォルトイベントをトリガーしません.*第二に、jQueryオブジェクトセットの最初の要素のイベント処理関数のみがトリガーされます.*第三に、この方法は、チェーン性のあるjQueryオブジェクトではなく、イベント処理関数の戻り値を返す.また、最初のjQueryオブジェクトのセットが空の場合、このメソッドはundefinedを返します.
//              ,          。          input   
$("input").triggerHandler("focus");

三、PromiseオブジェクトとDeferredオブジェクト
Promise jQueryのdeferredオブジェクトの詳細例:進捗通知
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>progress Demo</title>
    <script src="../../lib/jquery/dist/jquery.min.js"></script>
</head>
<body>
    <input type="text" id="number">
    <span id="tips"></span>

    <script> var $input = $("#number"), $tips = $("#tips"); var def = $.Deferred(); var goalCount = 20; def.progress(function(currentCount){ var percentComplete = Math.floor(currentCount / goalCount * 100); $tips.text(percentComplete + "% complete"); }); def.done(function(){ $tips.text("good job!"); }); $input.on("keypress", function(){ var count = $(this).val().split("").length; if(count >= goalCount) { def.resolve(); } // notify,      args             (progressCallbacks) def.notify(count); }); </script>
</body>
</html>

四、Async.jsのワークフロー制御
1.非同期関数を順次実行
非同期関数のセットを順次実行したいとします.
funcs[0](function(){
    funcs[1](function(){
        funcs[2](function(){
            ...
        });
    });
});
// async.js
var async = require("async");

var start = new Date().getTime();
async.series([
    function(callback){
        setTimeout(callback, 100);
    }, function(callback){
        setTimeout(callback, 200);
    }, function(callback){
        setTimeout(callback, 300);
    }
],function(err, result){
    console.log(new Date().getTime() - start + "ms");   // 612
});
async.series([
    function(callback){
        callback(null, 'one');
    }, function(callback){
        callback(null, 'two');
    }
],function(err, result){
    console.log(result);    // ["one", "two"]
});

2.非同期関数並列運転
var async = require("async");

var start = new Date().getTime();
async.parallel([
    function(callback){
        setTimeout(callback, 100);
    }, function(callback){
        setTimeout(callback, 200);
    }, function(callback){
        setTimeout(callback, 300);
    }
],function(err, result){
    console.log(new Date().getTime() - start + "ms");   // 312
});

3.極簡主義Stepのワークフロー制御
var fs = require("fs");
var path = require("path");
var Step = require("step"); // https://github.com/creationix/step

//      
Step(function readSelf() {
        fs.readFile(__filename, this);
    }, function capitalize(err, text) {
        if (err) throw err;
        return new Buffer(text).toString().toUpperCase();
    }, function showIt(err, newText) {
        if (err) throw err;
        console.log(newText);
    }
);
//     
Step(
    // Loads two files in parallel
    function loadStuff() {
        console.log(".."+__dirname)
        fs.readFile(__dirname + "/a.txt", 'UTF-8', this.parallel());
        fs.readFile(__dirname + "/b.txt", this.parallel());
    },
    // Show the result when done
    function showStuff(err, a, b) {
        if (err) throw err;
        console.log(a);
        console.log("=============");
        console.log(new Buffer(b).toString());
    }
);
//   
Step(
    function readDir() {
        fs.readdir(__dirname, this);
    },
    function readFiles(err, results) {
        if (err) throw err;
        // Create a new group
        var group = this.group();
        results.forEach(function (filename) {
            if (/\.js$/.test(filename)) {
                fs.readFile(__dirname + "/" + filename, 'utf8', group());
            }
        });
    },
    function showAll(err , files) {
        if (err) throw err;
        console.dir(files);
    }
);

五、workerオブジェクトのマルチスレッド技術
JavaScriptではイベントがマルチスレッド技術の代替品であることがよく見られます.しかし、より正確には、イベントは特殊なマルチスレッドに代わるしかありません.JavaScriptでは、ワークベンチを使用してI/O操作に似たインタラクティブなスレッドを個別に開くことができます.注:同じプロセス内の複数のスレッド間でステータスを共有できますが、互いに独立したプロセス間では共有できません.
1.ウェブ版workerオブジェクト
workerオブジェクトを生成するには、スクリプトURLをパラメータとしてグローバルWorkerコンストラクション関数を呼び出すだけです.
/* main.js */
var worker = new Worker("sub.js");
//   worker  
worker.addEventListener("message", function(e){
    //   sub   console.log(e.data);
});
//  sub     worker.postMessage("football");
worker.postMessage("baseball");
/* sub.js */
/** *  worker   ,              ,              ,         . *      worker          ? *        JavaScript      ,      . *   worker        ,         java  ,   DOM              ,      . *       ,worker          window         worker          . * worker             self,  self        . *   :setTimeout,XMLHttpRequest    */
self.addEventListener("message", function(e){
    self.postMessage(e.data);
});

2.clusterが持ってきたNode版worker
var cluster = require("cluster");

if(cluster.isMaster) {
    var coreCount = require("os").cpus().length;
    for(var i = 0; i < coreCount; i++) {
        var worker = cluster.fork();
        worker.send("Hello worker!");
        worker.on("message", function (message) {
            // Node  worker         ,     
            // {cmd: 'online', _queryId: 1, _workerId: 1}
            if(message._queryId) return;
            console.log(message);
        });
    }
}else {
    process.send("Hello, main process!");
    process.on("message", function (message) {
        console.log(message);
    })
}

注意:clusterは、同じスクリプトを同時に実行することをサポートし、スレッド間の通信オーバーヘッドを最小限に抑えるために、スレッド間共有の状態をRedisのような外部データベースに格納すべきである.
六、非同期のスクリプトロード
<script src="resource.js"></script>

ドキュメント<head>で上述したロードjsは同期ブロックロード(スクリプトのダウンロードが完了して実行されると、ブラウザは後続のリソースをロードします)であり、不要な問題を回避するために、すぐにロードしなければならないものを中に入れ、後でロードして<body>に入れることができます.
1.スクリプトの遅延運転
<script defer src="resource.js"></script>

ブラウザに「このスクリプトのロードをすぐに開始してください.ただし、ドキュメントが準備され、deferプロパティを持つすべてのスクリプトが実行されるまで待ってください」という通知に相当します.ドキュメント<head>のラベルに遅延スクリプトを入れると、<body>のラベルにスクリプトを配置する際のすべてのメリットと、大きなドキュメントのロード速度を大幅に向上させることができます.ヒント:現在、一部のブラウザではdeferがサポートされていないため、遅延スクリプトのコードを$(document).readyなどの構造にカプセル化することができます.
2.スクリプトの非同期実行
<script async src="resource.js"></script>

スクリプトは任意の順序で実行され、ドキュメントが準備されているかどうかにかかわらず、JavaScriptエンジンが使用可能であればすぐに実行されます.
注意:(1)この2つのプロパティを同時にサポートするブラウザで使用すると、asyncはdeferを上書きします.(2)非同期または遅延ロードを使用するスクリプトでは,document.writeは使用できず,予知できない動作を示す.
3.動的ロード・スクリプト
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = "resource.js";
head.appendChild(script);
script.onload = function(){
    //                
};

注意:onload互換性の問題なので、ここでは第三者ライブラリの使用をお勧めします.例えば、requirejs