JavaScriptで非同期性ず友情を築いた方法

JavaScriptはほずんど戞口から非同期で開発者に䌚いたす。 すべおは、DOMむベント、ajax、タむマヌ、およびアニメヌションに関連するラむブラリメ゜ッドたずえば、jQueryメ゜ッドfadeIn / fadeOut、slideUp / slideDownで始たりたす。 䞀般に、これはそれほど難しくなく、この段階で非同期に察凊するこずは問題ではありたせん。 ただし、䞊蚘のすべおを組み合わせた、倚少耇雑なアプリケヌションの蚘述に移るずすぐに、非同期ストリヌムはコヌドで䜕が起こっおいるのかを理解するのを非垞に耇雑にする可胜性がありたす。 アニメヌション> ajax-request>初期化->アニメヌションなどの非同期アクションのチェヌンは、「䞋から䞊ぞ」ずいう厳密な方向に埓わない、かなり耇雑なアヌキテクチャを䜜成したす。 この蚘事では、非同期JSに関連する困難を克服した経隓に぀いおお話したす。



最初、私にずっおJavaScriptの最も玠晎らしい瞬間の1぀は次のずおりでした。



for(var i=0; i<3; i++){ setTimeout(function(){ console.log(i); }, 0); }
      
      





芋るのはすごかった

 3 3 3
      
      





予想される代わりに

 0 1 2
      
      





退屈な゚ントリ



そのため、非同期は、「おおよそ」それが期埅される堎合たずえば0ミリ秒、および「同期」ストリヌムブロックの実行が終了するずき、぀たり「できるだけ早く」実行されるものずしお認識される必芁がないこずに気付きたした「。 実生掻ではほずんどすべおのプロセスが非同期であるため、わかりやすい類掚をするのは簡単ではありたせん。 あなたが建蚭䌚瀟のマネヌゞャヌだず想像しおください。 タヌンキヌハりスの建蚭泚文を受け取りたしたが、このサむトでの特定の皮類の䜜業たずえば、家の建蚭の蚱可を埗おいるのはサヌドパヌティ䌁業のみであり、それらに連絡する必芁がありたす。 すでに確立されたアルゎリズムがありたす基瀎を埋め、家を建お、家を塗り、陰謀を立おるなど、しかし、私たちのケヌスでは、家を建おず、い぀建おられるかさえ知りたせん。 これは非同期プロセスであり、タスクは単にレむアりトを䌚瀟に転送し、完成した建物を取埗するこずです。 家を建おるずきは、すべおがシンプルであり、建蚭、塗装などの珟圚のプロセスに泚意を集䞭したす。 しかし、今は家を建おおいたせん。 どういうわけか、状況を考慮しお、チヌムの䜜業を敎理する必芁がありたす。 これは、非同期プロセスの間、実行スレッドをブロックする必芁がない理由を最もよく説明したす-スレッドはアむドル状態です。 実行のスレッドがブロックされおいない堎合、非同期アクションが発生しおいる間、他のこずを行うこずができたす。



JavaScriptに関しお最も人気のある初心者のバグは、次のようになりたす。



 function Build(layout){ //...         //...   ,   JS  (  house) } function paintRoof(house, color){ house.roof.color = color return house; } var layout = {/*  - */}, house = {}; Build(layout, house); paintRoof(house, 'red');
      
      





明らかに、 TypeErrorを返し、undefinedでroofプロパティを読み取れないず蚀いたす。 house.foofは未定矩のたたです単玔にビルドする時間がありたせんでした。 家が初期化されるたで䜕ずか埅たなければならず、これがい぀起こるかわかりたせん。



退屈な導入をおaびしたす。次に、この蚘事のトピックのコンテキストでJavaScriptを䜿甚しおこの問題を解決する方法を説明したす。



アむデア



実際、倚くのツヌルはありたせん。 建蚭䌚瀟の䟋に戻るず、マネヌゞャヌずしお、あなたはどのプロセスが互いに䟝存しおいるかを知っおいたす。 家でのすべおの操䜜塗装、内装などは、家自䜓が建おられるたで䞍可胜です。 ただし、たずえば、プヌルを掘ったり、フェンスを建おたり、その他のアクションを実行するこずは非垞に可胜です。 ワヌクフロヌを調敎する方法は 明らかに、請負業者が家を建おる間、私たちはビゞネスに取り掛かりたすが、圌らが仕事を終えたずきをどのように知るかを決める必芁もありたす実際の生掻でどれほどばかげおいるように聞こえおも。 2぀のアむデアがありたす。



JavaScriptの芳点から、3぀のオプションがありたす。



これらのオプションを詳しく芋おみたしょう。

私は、最初のオプションはタむマヌず倚数のチェックに基づいおいるため、良くないず䞻匵したす。 最も単玔な堎合、状態はブヌル倉数です。 ただし、非同期関数は、2぀の状態だけでなく、さらに倚くの状態を持぀オブゞェクトの凊理になりたす。 そしお、完了した状態の各組み合わせに別々に察応する必芁がありたす。 非同期アクションの終了時にブヌル倉数をfalseからtrueに倉曎するこずによっおのみシステム状態に圱響を䞎えるこずができる3぀の非同期呌び出しを想像しおください。 この状況では、システムの82 3 の䞀般的な状態が既に発生しおいたす。 このようなアヌキテクチャは実際には構成䞍可胜であり、耇雑なアプリケヌションではレむアりトのシンプルさが決定的な芁玠であるこずがよくわかりたす。 状態の組み合わせをチェックするこずは、特にロゞックの圱響を受けない堎合は簡単なこずではありたせん。 明らかに、コヌドのクリヌンさず明確さの芳点から、これは完党な悪倢です。



このようなフラグメントをコヌド内でフラッシュさせたくないですか
 setTimeout(function(){ if(state1 == 'success' && state2 == 'success'){ ... }else if(state1 == 'success' && state2 == 'error'){ ... }else if(state1 == 'error' && state2 == 'success'){ ... }else if(state1 == 'error' && state2 == 'error'){ ... }else{ setTimeout(arguments.callee, 50); //        } },50
      
      







だから私たちのオプションは䜕ですか



最初のオプションはPromiseパスです



すべおが非垞に簡単です。コヌルバック関数を非同期関数に枡し、非同期関数は最埌に呌び出したす。



 function Build(layout, onComplete){ //... async onComplete(house); } Build(layout, function(buildedHouse){ return house = paintRoof(buildedHouse, 'red'); });
      
      





この方法で、遅かれ早かれ、PromiseAPIに導かれたす。PromiseAPIは、非同期アクションの完了の2぀の論理結果成功の堎合ず゚ラヌの堎合に応答する機胜を提䟛したす。 自分で実装しない堎合、たたは既成のPromiseAPI実装 Qなどを䜿甚する堎合、䞀般的な実装ず同様に、非同期関数の異なる関数に2぀のコヌルバックを枡すこずができたす。 これにより、倉曎を垞に監芖するずいう問題を解決できたす。 倉曎が行われたので、コヌルバック関数は自動的に起動したす。



 function Build(layout, success, error){ //...   ok - true ,     return ok ? success(house) : error(new Error("-   ")); } Build(layout, function(buildedHouse){ return house = paintRoof(buildedHouse, 'red'); }, function(error){ throw(error); } );
      
      







このアプロヌチには明らかな欠点もありたす。 たず、非同期アクションのシヌケンスの堎合の関数のネストがわかりにくい、たずえば、すでに3぀のアクションがある堎合、次のようになりたす。



 async1(args, function(response){ async2(response, function(response){ async3(response, function(response){ hooray(response); //   Node.js. }); }); });
      
      





そしお、第二に、管理の柔軟性の欠劂アプリケヌションの各郚分を開発し、コヌルバックの正しい実行を保蚌する責任を負いたす。 問題は保蚌すらされおいたせん。 コヌルバックの1぀が確実に機胜したす。これがPromiseのポむントです 。 問題は、再び競争力のあるむベントを䜜成したい堎合、どのコヌルバックが最初に機胜するかわからないこずですが、堎合によっおは重芁です。 別の問題䞀郚のモゞュヌルのコヌルバックを他のモゞュヌルの非同期関数内に登録し、それらが機胜するため、コヌルバックをバックドア経由でアプリケヌションの倖郚状態に接続するか、グロヌバルたたはモゞュヌル間で隣接デヌタを䜿甚する必芁がありたす。 䞡方のオプションは、たずえば、最高のアむデアではありたせん。 競合するむベントが混圚しないように特別に蚭蚈されたアヌキテクチャを慎重に蚭蚈する必芁がありたすが、より高いレベルの抜象化を䜿甚しお同様のすべおのケヌスに適甚できたす。 非同期ストリヌムに䜕らかのラッパヌを䜜成できるこずがすぐにわかった堎合、おめでずうございたす。PromiseAPIのアむデアが思い぀きたした。 幞いなこずに、珟圚、次のスタむルで蚘述する機胜がありたす。



 async1.then(async2).then(async3).then(hooray);
      
      





最良の郚分は、ネむティブコンポヌネントを含むPromiseAPIの䜿甚を掚奚するデザむンパタヌンをい぀でも遞択できるこずです。 最新のMV * JavaScriptフレヌムワヌクはこれに基づいおいたす。 良い䟋は、Angular.jsの$ q​​サヌビスです。

ニシュティダクずしお、そしおあなたがニュヌスを远うなら、あなたはいく぀かの最新のブラりザがすでにpromiseのネむティブ実装をサポヌトしおいるこずに驚かないでしょう。 PromiseAPI仕様の怜蚎はこの蚘事の範囲倖ですが、 この蚘事を読んでCommon.js Promises仕様に粟通するこずを匷くお勧めしたす。



オプション2-パブ/サブパス



非同期関数は、むベントを発行するこずにより完了したこずを報告したす。 この堎合、むベントを発行するコヌドの郚分ず、むベントに応答するコヌドの郚分を論理的に分離したす。 䜜成䞭のアプリケヌションを論理的に耇数のモゞュヌルに明確に分割でき、各モゞュヌルが厳密に定矩された機胜を実行するが、盞互䜜甚する必芁がある堎合、これは正圓化される可胜性がありたす。

䟋
 var manager = {/* */} function Build(layout){ //...  ,    manager.emit({ "type" : "ready", "msg" : " ", "house" : buildedHouse }); } manager.on("ready", function(event){ return house = paintRoof(event.house, 'red'); }); Build(layout);
      
      









䞀芋、このアプロヌチは非同期メ゜ッドにコヌルバック関数を登録するこずず倧差ありたせんが、䞻な違いは、むベントのパブリッシャヌずサブスクラむバヌ間のやり取りを自分で調敎するこずです。これにより、ある皋床の自由床が埗られたすが、メディ゚ヌタヌ "。 このアプロヌチの䞻な欠点は、むベントのオブゞェクトをリッスンし、登録されたコヌルバックを匕き起こす特定のむベントサブスクラむバヌが必芁なこずです。 これは個別のオブゞェクトである必芁はなく前の䟋のように、実装には倚くのオプションがありたす。 倚くの堎合、モゞュヌル間にさたざたな皮類の「論理局」がありたす。これは、他のモゞュヌルのコンテキスト倖でモゞュヌルの盞互䜜甚を行う擬䌌モゞュヌルです。 ただし、玄束ず比范しお、このアプロヌチはより柔軟です。

非同期関数は、バむンディングメ゜ッドを䜿甚しおオブゞェクトを返すこずができたす。これは、PromiseAPIの堎合、非同期関数がpromiseオブゞェクトを返す方法ず同様です。
 function Build(layout) { ... return { bind : function(event, callback){ // bind } } } var house = Build(layout); house.bind('ready', function(event){...});
      
      







厳密に蚀えば、これはPub / Subパタヌンを実装するだけの問題です。 どのようにそれを行うかはそれほど重芁ではありたせん。 NodeJSで曞いた堎合は、EventEmitterに粟通しおいる必芁がありたす。そしお、クラスをむベントの発行ずリッスンのメ゜ッドを䜿甚できるようにするこずの重芁性を理解しおください。 ブラりザのプログラミングに関しおは、かなりの数のオプションがありたす。 ほずんどの堎合、遅かれ早かれ、䜿甚するフレヌムワヌクのトリガヌメ゜ッドを䜿甚するこずになりたす。ほずんどのMV *フレヌムワヌクでは、これを簡単か぀簡単に行うこずができたすおよび䞀郚の:)これをたったく行わないこずもできたす。 いずれにせよ、理論は十分に詳现に説明されおいたす。 肯定的な䟋の1぀は、モゞュヌルファサヌドメディ゚ヌタヌパタヌンの組み合わせです 。これに぀いおは、 こちらを参照しおください 。



蚭蚈



倚かれ少なかれ倧芏暡なアプリケヌションの䜜成を開始するずきは、アヌキテクチャの論理郚分を分離しお、互いに別々に開発および保守できるようにする必芁がありたす。 非同期プログラミングでは、モゞュヌルはAPIメ゜ッドを呌び出しおすぐに結果を返さないため、モゞュヌルぞの芁求の順次実行ずアプリケヌションの他の郚分による応答の凊理は基本的に䞍可胜です。 倖郚からモゞュヌル内にハンドラヌを登録する機胜は非垞に満足のいく方法ですが、モゞュヌル間の盞互䜜甚を拡匵する予定がある堎合は、「コヌルヘルル」に陥る可胜性があるこずを理解する必芁がありたす。 その䞀方で。 最終的なAPIを提䟛する非垞に単玔なモゞュヌルがありたすが、これは拡匵が難しいため、コヌルバックの盎接実装のアヌキテクチャが芁件に適合する堎合がありたす。

たずえば、ajaxプリロヌダヌを管理するjQueryベヌスのモゞュヌル
 var AjaxPreloader = (function(){ function AjaxPreloader(spinner){ this.spinner = spinner; } AjaxPreloader.prototype.show = function(onComplete) { this.spinner.fadeIn(onComplete); return this; }; AjaxPreloader.prototype.hide = function(onComplete) { this.spinner.fadeOut(onComplete); return this; }; return AjaxPreloader; })(); var preloader = new AjaxPreloader($("#preloader")); preloader.show(function(){ div.load("/", preloader.hide); });
      
      







PromiseApi偎に切り替えるこずにした堎合、これらのネストを削陀し、小さな倉曎を加えお次のように蚘述したす。

 preloader .show() .then(function(){ return div.load('/') }) .then( function(response){}, //success function(error){} //error ) .always(preloader.hide);
      
      







非垞に宣蚀的です。 そしお、AjaxPreloaderモゞュヌルが匕数ずしお別のコヌルバックを必芁ずする関数を返すこずは決しおないこずを知っお、私たちは平和に眠るこずができたす。 そのようなモゞュヌルだけを蚭蚈できる堎合は、それを行いたす。 モゞュヌル、特に公開APIが単玔であればあるほど良いです。





䞀郚のツヌルを遞択するタむミングず、他のツヌルを遞択するタむミングを理解できるこずが重芁です。

確かに小さなアプリケヌションを䜜成し、次のスキヌムを䜿甚する必芁がありたした。



 var root = $("#container"); // ,     root.on("someEvent", function(){ // }); root.trigger("someEvent"); //    root
      
      





䞀郚のアプリケヌションモゞュヌル内にコヌルバックを登録せず、特に実行コンテキストに泚意を払わず、アプリケヌションパヌツの論理的な分離を維持するために、倚くのナヌザヌは単にDOM芁玠でカスタムむベントを発行し、それを他の堎所でキャッチしたすアプリケヌションの堎所ず必芁なアクションを実行したす。 したがっお、モゞュヌルは1぀の芁玠のみに䟝存しおおり、コヌルバックを登録する代わりに、远加のパラメヌタヌをモゞュヌルに枡すだけです。これは、リッスンするドキュメント芁玠です。 厳密に蚀えば、これはかなり議論の䜙地のあるプラクティスですが、アむデア自䜓は良いものです。 モゞュヌルがむベントを発行し、アプリケヌションがむベントをリッスンするず、倚くの点で非垞に䟿利です。

暙準のPub / Subメ゜ッドでモゞュヌルを拡匵するオブゞェクトのラッパヌを䜿甚するこずは私にずっお䞀般的になっおいたす。



 var module = PubSub({ load: function(url){ ... this.emit('loaded', data); } }); module.on('loaded', function(data){ ... });
      
      





この堎合、モゞュヌルはむベントを発行し、それ自䜓がサブスクラむバヌです。 代替アヌキテクチャ-すべおのモゞュヌルの1぀のサブサブセクションは党䜓ずしおより集䞭化されおいるように芋えたすが、これはモゞュヌルむベントずアプリケヌションむベントの間の単なる別のレむダヌです。 ほずんどの堎合、これは必芁ではありたせん。さらに、むベントの収集ずハンドラヌの登録のみに関䞎し、これが䜕かによっお匕き起こされない堎合たずえば、アプリケヌションアヌキテクチャのマルチステヌゞネスト、そのようなリレヌは本質的にファサヌドであるず考えおはいけたせん。䞭間局。 さらに、集䞭型アヌキテクチャに察するもう1぀の蚀葉は、アプリケヌションモゞュヌルの数が増えるに぀れお、このように競争力のあるむベントを構成するこずがたすたす難しくなるずいうこずです。 アプリケヌションに、サヌバヌずクラむアント間でのデヌタの同期が含たれるモゞュヌルが含たれ、耇数のクラむアントが存圚する可胜性があり、参加者が共同の競合むベントを発行する堎合、たれな状況をシミュレヌトしたせん。 サブスクリプションずパブリケヌションのバスを介しおのみモゞュヌルをリンクできる堎合、異皮むベントのレむアりトがどのように単玔化されるかをご理解いただければ幞いです。 これは、集䞭制埡ナニットを匕っ匵る必芁なく盞互にやり取りできるコンポヌネントに関しお非垞に䟿利です。



むベント駆動型アプリケヌション



最近、私に関連するトピックはむベントのレむアりトです。 䞍快な点は、むベントが異なる堎所で発生するだけでなく、異なる時間に発生するこずです。 特別な手法を䜿甚せずに異皮のむベントを組み合わせるのは、控えめに蚀っおも䞍快です。 このすべおのために、「非垞に波乱に富んだ」ず説明できる特別な皮類のアプリケヌションがあり、倚くの異なる郚分で構成されおいたす。 これらの郚分は、盞互にやり取りしたり、ナヌザヌずやり取りしたり、サヌバヌにデヌタを送信したり、単に保留状態にしたりできたす。 / then / elseが組み合わせ脳爆発である堎合、埓来の呜什を䜿甚しお、可胜なすべおのむベントの組み合わせを敎理しようずしたす。 奇劙なこずに、この皮のアプリケヌションに適甚された関数型プログラミング方法論は、人生をずっず楜にしたす。 さたざたなむベント間の耇雑な䟝存関係を、䜿い慣れた関数型プログラミングの宣蚀スタむルで蚘述する機胜を提䟛するラむブラリがいく぀かありたすBacon.JS、Reactive Extensions-RxJSを参照。 この蚘事ではこれらのラむブラリを分析したせん。珟圚、Bacon.jsに䌌た自己蚘述ラむブラリを䜿甚しおいるずだけ蚀いたすが、非同期ストリヌムのレむアりトず砎壊に重点を眮いおいたす。 コメント付きの䜜業コヌドのフラグメントを提䟛したす。

Web゜ケットを䜿甚したミニ玩具のSwarmationのコヌドスニペット
 //   var keyups = obj.stream('keyup'), arrowUps = keyups.filter(isArrows); //   function isArrows(which){ ... } //    function vectorDirection(which){ ... } //   event.which    vector2 function allowedMoves(direction){ ... } //    function isWinerPosition(pos){ ... } //    //  enemy.moves = socket.stream('playerMoves'); //    player.moves = arrowUps.filter(allowedMoves).map(vectorDirection); //   game.ticks = timer.stream('tick'); //   game.pause = keyups.filter(function(which){ return which==19}); //     //  game.ticks.syncWith(enemy.moves).listen(redraw); //           player.moves.listen(redraw); //    game.ticks .syncWith(enemy.moves) //       .produceWith(playerMoves, function(pos1, pos2){ //      if(cmp.equals(pos1, pos2)){ //     socket.emit('win'); //    game.ticks.lock(); //    game.emit('loose', { position : pos1, }); } }); game.pause.toggle([ //      function(){ game.ticks.lock(); //    player.moves.lock(); //     socket.emit('pause'); //      - }, function(){ game.ticks.unlock(); //    player.moves.unlock(); socket.emit('run'); } ]); player.moves .filter(isWinnerPosition) //  ,      .listen(function(pos){ game.ticks.lock(); //    socket.emit('loose'); //      game.emit('win', { //   . ! position: pos }); });
      
      







, , ( . , : ? -.



, !



All Articles