ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptの非同期プログラミング技術を詳しく解説

JavaScriptの非同期プログラミング技術を詳しく解説

黄舟
黄舟オリジナル
2017-02-27 14:23:411283ブラウズ

JavaScript 非同期プログラミング技術の詳細な説明

ブラウザのイベント ポーリング メカニズム (および Node.js のイベント ポーリング メカニズム) に基づいて、JavaScript は多くの場合、非同期環境で実行されます。 JavaScript 独自の言語の特性 (プログラマがスレッド/プロセスを制御する必要がない) により、js で非同期プログラミングを解決することが非常に重要です。完全なプロジェクトでは、JS 開発者が非同期操作に直面しないことは不可能であると言えます。この記事では、いくつかの古典的な JavaScript 非同期プログラミングのシリアル化メソッドを詳細に紹介し、ES6 が提供する Promise 逐次実行メソッドについても簡単に紹介します。

1. コールバック関数

(1) 古典的なコールバック関数メソッド: ネストされたインライン関数

URL パラメーターを受け取り、アドレスへの呼び出しを開始する ajax() メソッドがあるとします。 request、2 番目のパラメータ - リクエストの最後にコールバック関数が実行されます: ajax()方法,他接收一个url参数,向该地址发起一个异步请求,在请求结束时执行第二个参数—一个回调函数:

ajax(url,function(result){
    console.log(result);
});

可以说这种方式几乎是每个前端开发人员都用过的回调函数方式,有了这样的回调机制,开发人员就不用编写类似下面这样的代码来推测服务器请求什么时候返回:

var result=ajax(url);
setTimeout(function(result){
    console.log(result);
},400);

大家应该能明白我此处想表达的意思。我们设置了一个延迟400毫秒的定时器,来假设我们发出的ajax请求会在400毫秒之内完成。否则,我们将会操作一个undefinedresult
但是有一个问题随着项目的扩大渐渐浮现出来:如果场景需要我们多层嵌套回调函数,代码将变得难以阅读和维护:

ajax(url0,function(result0){
    ajax(result0.url1,function(result1){
        ajax(result1.url2,function(result2){
            console.log(result2);
        });
    });
});

(2)调用外部函数

为了解决内联回调函数暴露出来的代码混乱问题,我们引入外部函数调用来解决类似问题:

function handle2(result){
    console.log(result);
}function handle1(result){
    ajax(result.url,function(result){
        handle2(result);
    });
}
ajax(url,function(result){
    handle1(result);
});

通过这种拆分内联函数,来调用外部函数的优化方法,能极大的保持代码的简洁性。

二.制定回调管理器

观察流行的JavaScript流程控制工具,例如Nimble、Step、Seq,我们会学习到一种简洁的设计模式:通过回调管理器来控制异步JavaScript执行流程,下面是一个典型的回调管理器的关键代码示例:

var Flow={};//设置next方法,在上一个方法完成时调用下一个方法Flow.next=function(){
    if(this.stack[0]){        //弹出方法栈中的第一个方法,并执行他
        this.stack.shift()();
    }
};//设置series方法,接收一个函数数组,并按序执行Flow.series=function(arr){
    this.stack=arr;    this.next();
};//通过Flow.series我们能够控制传入的函数的执行顺序Flow.series([        function(){
            //do something
            console.log(1);
            Flow.next();
        },        function(next){
            //do something
            console.log(2);
            Flow.next();
        }
]);

我们初始化了一个Flow控制器,为他设计了seriesnext两个函数属性。在我们编写的业务方法内部,在方法结尾处通过调用Flow.next()的方式来顺序触发下一个方法;通过执行series方法来顺序执行异步函数。这种通过核心控制器来管理异步函数调用的方式简化了我们的编程过程,让开发人员能够投入更多精力在业务逻辑上。

三.全局标记控制

(1)简单计数器控制

也许上面介绍的异步方法仍然不能满足实际开发中的业务场景:假设我们有a()b()c()三个方法,a和b没有依赖关系,可以异步进行。但是c必须在a和b都完成之后才能触发。为满足这样的逻辑实现,我们加入一个全局计数器来控制代码的执行流程:

var flag=2;var aValue,bValue;function a(){
    aValue=1;
    flag--;
    c();
}function b(){
    setTimeout(function(){
        bValue=2;
        flag--;
        c();
    },200);
}function c(){
    if(flag==0){
        console.log("after a and b:"+(aValue+bValue));
    }
}
a();
b();

我们设置了一个全局变量flag来监控方法a和方法b的完成情况。方法b通过设置一个200毫秒的定时器来模拟网络环境,最终会在b方法执行完成之后成功调用c方法。这样我们就实现了对方法a()b()c()的依赖调用。

(2)面向数据的控制

当上述方案在复杂场景下应用时,会出现如下问题:产品经过多个版本迭代,c方法依赖更多的方法,因此计数器flag需要不断的变化;产品迭代过程中更换了开发人员。当出现上述两种情况时,代码的逻辑将会变得混乱不堪,flag标记符是否能保持简明正确很大程度上受到了产品迭代的影响。因此我们提出面向数据的优化改进。
在真实的开发场景中,存在方法依赖的原因基本都是因为存在数据依赖,对于上面那个简单的示例:c方法依赖于a方法和b方法操作的结果,而不是依赖于flag是否为0。因此我们可以通过检查依赖方法是否已经完成了数据处理来代替检查标记符是否已经被置为0,在这个例子中也就是在c方法中检查aValue和bValue是否已经完成了赋值:

function c(){
    if(aValue!==undefined && bValue!==undefined){
        console.log("after a and b:"+(aValue+bValue));
    }
}

针对更加通用的场景,我们将上述代码修改为下:

var checkDependency={};var aValue,bValue;function a(){
    aValue=1;
    checkDependency.a=true;
    c();
}function b(){
    setTimeout(function(){
        bValue=2;
        checkDependency.b=true;
        c();
    },200);
}function c(){
    if(checkDependency.a && checkDependency.b){
        console.log("after a and b:"+(aValue+bValue));
    }
}
a();
b();

通过面向数据的检查方式,未来扩展时,我们仅需要在新增的方法中增加对checkDependency

var bool=false;/*
 * 新建一个Promise实例,向构造函数传入一个异步执行函数
 * 异步函数会接受两个参数,由Promise传入,对应then方法中传入的方法
 */var promise=new Promise(function(resolve,reject){
    setTimeout(function(){
        if(bool){            //根据执行情况相应调用resolve和reject
            resolve(bool);
        }else{
            reject(bool);
        }
    },200);
});//通过then向Promise实例传入解决方法promise.then(function resolve(result){
    console.log("success");
},function reject(result){
    console.log("failure");
});

このメソッドは、ほぼすべてのフロントエンド開発者が使用するコールバック関数メソッドであると言えます。このようなコールバック メカニズムを使用すると、開発者は次のことが可能になります。サーバー リクエストがいつ返されるかを推測するために次のようなコードを記述する必要はありません:

new Promise(function(res,rej){
    if(/*异步调用成功*/){
        res(data);
    }else{
        rej(error);
    }
}).then(function resolve(result){
    console.log("success");
},function reject(result){
    console.log("failure");
});

ここで私が何を表現したいのか理解していただけるはずです。行う ajax リクエストが 400 ミリ秒以内に完了すると仮定して、400 ミリ秒の遅延を持つタイマーを設定します。それ以外の場合は、未定義 result を操作します。
しかし、プロジェクトが拡大するにつれて徐々に現れる問題があります。シーンで複数の層のネストされたコールバック関数が必要な場合、コードの読み取りと保守が困難になります:

rrreee

(2) 外部関数の呼び出し

インライン コールバック関数によって明らかにされるコードの混乱の問題を解決するために、同様の問題を解決するために外部関数呼び出しを導入します: 🎜rrreee🎜インライン関数を分割し、外部関数を呼び出すことにより、この最適化方法はコードの単純さを大幅に維持できます。 。 🎜🎜2. コールバック マネージャーを開発する🎜🎜 Nimble、Step、Seq などの一般的な JavaScript プロセス制御ツールを観察して、コールバック マネージャーを使用して非同期 JavaScript 実行プロセスを制御する簡単な設計パターンを学びます。典型的なコールバック マネージャーの主なコード例: 🎜rrreee🎜 Flow コントローラーを初期化し、その関数属性用に 2 つの seriesnext を設計しました。作成したビジネス メソッド内では、メソッドの最後に Flow.next() を呼び出すことで次のメソッドが順番にトリガーされ、series を実行することで非同期実行が順番に実行されます。メソッド関数。コア コントローラーを介して非同期関数呼び出しを管理するこの方法により、プログラミング プロセスが簡素化され、開発者はビジネス ロジックにより多くのエネルギーを注ぐことができます。 🎜🎜3. グローバルマーク制御🎜🎜 (1) 単純なカウンタ制御🎜🎜おそらく、上記で紹介した非同期メソッドは、実際の開発のビジネスシナリオにまだ対応できません: a(), があるとします。 b()c() の 3 つのメソッドがあります。a と b には依存関係がなく、非同期で実行できます。ただし、c は、a と b の両方が完了した後にのみトリガーできます。このような論理的な実装を満たすために、コードの実行フローを制御するグローバル カウンターを追加します。 🎜rrreee🎜 メソッド a とメソッド b の完了を監視するグローバル変数フラグを設定します。メソッド b は、200 ミリ秒のタイマーを設定してネットワーク環境をシミュレートし、メソッド b の実行後に最終的にメソッド c を正常に呼び出します。このようにして、メソッド a()b()c() への依存呼び出しを実現します。 🎜🎜(2) データ指向制御🎜🎜 上記のソリューションを複雑なシナリオに適用すると、次の問題が発生します: 製品は複数のバージョンの反復を経ており、c メソッドはより多くのメソッドに依存しているため、カウンター フラグが必要になります。製品開発者は反復中に常に変更されます。上記の 2 つの状況が発生すると、コードのロジックが混乱し、フラグ タグが簡潔かつ正確なままであるかどうかは、製品の反復に大きく影響されます。したがって、データ指向の最適化の改善を提案します。
実際の開発シナリオでは、メソッドの依存関係が存在する理由は、基本的にはデータの依存関係が存在するためです。上記の簡単な例では、メソッド c はメソッド a とメソッド b の操作の結果に依存します。フラグが 0 であるかどうかよりも。したがって、マーカーが 0 に設定されているかどうかを確認することを、依存するメソッドがデータ処理を完了したかどうかを確認することで置き換えることができます。この例では、c メソッドで aValue と bValue が代入を完了したかどうかを確認することになります。一般的なシナリオでは、上記のコードを次のように変更します。 🎜rrreee🎜 データ指向のチェック方法を使用することで、将来拡張するときに、 checkDependency オブジェクトに変更を追加するだけで済みます。新しいメソッド、および非同期の依存メソッドの順次実行は、c メソッド内の対応する属性の存在を確認することで実現できます。 🎜🎜IV. ES6 の新しいメソッド - Promise クラス 🎜🎜 JavaScript の非同期メソッドの複雑さを解決するために、公式は統合制御メソッドを導入しました: 🎜rrreee🎜 上記のコード例は、おそらく実際のシナリオでの基本的な Promise アプリケーションを示しています。より一般的なのは、次のチェーン コールです: 🎜
new Promise(function(res,rej){
    if(/*异步调用成功*/){
        res(data);
    }else{
        rej(error);
    }
}).then(function resolve(result){
    console.log("success");
},function reject(result){
    console.log("failure");
});

如果对Promise感兴趣的话,可以在网上寻找资料继续深入学习!
关于Promise的兼容性,通常web前端JavaScript代码中不会直接使用Promise(通过caniuse.com网站查询发现Android4.4不支持Promise)。如果特别想使用的,往往会在项目中附带一些补足兼容性的promise类库;而后端Node.js可以放心使用Promise类来管理异步逻辑。
JavaScriptの非同期プログラミング技術を詳しく解説

 以上就是详解JavaScript异步编程技术的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。