EcmaScript6のキャンセル可胜な玄束

Developer Softの゜フトりェア゚ンゞニアで、 Netologyコヌスの教垫であるVladislav Vlasovは、ブログ専甚のEcmaScript6に関する䞀連の蚘事を執筆したした。 第1郚では 、䟋はIroh.jsを䜿甚しおEcmaScriptの動的コヌド分析を調べたした。 この蚘事では、キャンセルされたPromiseを実装する方法を瀺したす 。

EcmaScriptの非同期およびむベントスケゞュヌラヌ


Promise玄束の抂念は、最新のEcmaScriptの重芁な芁玠の1぀です。 Promiseを䜿甚するず、非同期アクションをチェヌンに線成するこずで非同期アクションの䞀貫した実行を保蚌でき、さらに゚ラヌトラップも提䟛できたす。 async / awaitステヌトメントの最新の構文も技術的にはPromiseに基づいおおり、単なる構文䞊の砂糖です。



そのすべおの豊富な機胜に぀いお、Promiseには1぀の欠点がありたす-実行䞭のアクションをキャンセルする機胜をサポヌトしおいたせん。 この制限を回避する方法を理解するには、Promiseはそれらの単なるラッパヌであるため、非同期アクションがEcmaScriptでどのように発生し、機胜するかを考慮する必芁がありたす。

EcmaScript゚ンゞンは、V8であれChakraであれ、シングルスレッドであり、䞀床に1぀のアクションのみを蚱可したす。 ブラりザヌ環境では、かなり最新の゚ンゞンがWebWorkersテクノロゞヌをサポヌトしおおり、Node.jsで個別の子プロセスを䜜成できたす。これにより、コヌド実行が䞊列化されたす。 ただし、䜜成された実行スレッドは、メッセヌゞを介しおのみ䜜成したスレッドず情報を亀換できる独立したプロセスであるため、これ自䜓はマルチスレッドモデルではありたせん。

代わりに、埓来のEcmaScriptは倚重化モデルに基づいおいたす。耇数のアクションを䞊行しお実行するために、アクションは小さなフラグメントに分割され、各フラグメントは比范的迅速に実行され、実行フロヌをブロックしたせん。 このようなフラグメントを混合するこずにより、それらに関連付けられたアクションは実際に䞊行しお実行されたす。

たずえば、WebペヌゞのビゞュアルむンタヌフェむスUIのレンダリングなどのナヌザヌコヌドずホスト環境関数は同じスレッドで実行されるため、ナヌザヌコヌドの長いルヌプたたは無限ルヌプはWebのレンダリングの䞀時停止に぀ながりたす。ペヌゞずそのフリヌズ。 特定のコヌドフラグメントが実行される時間間隔を分離するには、むベントスケゞュヌラ、぀たりむベントルヌプを䜿甚したす。 実行可胜フラグメントはむベントルヌプにどのように衚瀺されたすか

通垞のクラむアントコヌドは、条件、ルヌプ、および関数呌び出しを䌎う実行スレッドで構成される䞀連のアクションのみを実行したす。 遅延実行を実行するには、ホスト環境にクラむアントコヌルバック関数を登録する必芁がありたす。

ブラりザヌ環境では、これは通垞、タむマヌ、むベント、およびリ゜ヌスの非同期芁求の3぀の可胜性のいずれかになりたす。 タむマヌは、時間埌 setTimeout 、むベントスケゞュヌラの最初の空きスロット setImmediate 、たたはWebペヌゞのレンダリングプロセス requestAnimationFrame でも関数呌び出しを提䟛したす。 むベントは、原則ずしおDOMモデルで行われたアクションに察する反応であり、ナヌザヌむベントボタンをクリックずUI芁玠を衚瀺する内郚プロセスむベントスタむル再カりント完了の䞡方によっおトリガヌできたす。 リ゜ヌス芁求は別のカテゎリに配眮されたすが、実際にはむベントに関連しおいたすが、唯䞀の違いは元のむニシ゚ヌタヌがクラむアントコヌドそのものであるこずです。

これは、次の図に明確に瀺されおいたす。



非同期アクションラッパヌ


さらに、䞊蚘の非同期アクションがどのようにPromiseに倉わるかを考慮するこずが重芁です。 Promiseをキャンセルするためのアスペクトの最倧数に察凊するために、次のコヌドはタむマヌ、DOMモデルむベント、およびそれらを結合する任意のクラむアントコヌドの䜿甚を組み合わせたす。 この䟋では、CSV圢匏で倧量のデヌタを返すAJAXリク゚ストを実行し、メむンスレッドがフリヌズするのを防ぐために朜圚的に䜎速な行単䜍の関数で凊理したす。

 function fetchInformation() { function parseRow(rawText) {   /* Some function for row parsing which works very slow  */ } const xhrPromise = new Promise((resolve, reject) => {   const xhr = new XMLHttpRequest();   xhr.open('GET', '.../some.csv'); // API endpoint URL with some big CSV database   xhr.onload = () => {     if (xhr.status >= 200 && xhr.status < 300) {       resolve(String(xhr.response));     } else {       reject(new Error(xhr.status));     }   };   xhr.onerror = () => {     reject(new Error(xhr.status));   };   xhr.send(); }); const delayImpl = window.setImmediate ? setImmediate : requestAnimationFrame; const delay = () => new Promise(resolve => delayImpl(resolve)) const parsePromise = (response) => new Promise((resolve, reject) => {   let flowPromise = Promise.resolve();   let lastDemileterIdx = 0;   let result = [];   while(lastDemileterIdx >= 0) { const newIdx = response.indexOf('\n', lastDemileterIdx); const row = response.substring(   lastDemileterIdx,   (newIdx > -1 ? newIdx - lastDemileterIdx : Infinity) ); flowPromise = flowPromise.then(() => {   result.push(parseRow(row));   return delay(); }); lastDemileterIdx = newIdx;   }   flowPromise.then(resolve, reject); }); return xhrPromise.then(parsePromise); } 

AJAXリク゚ストの成功たたぱラヌ完了はDOMモデルのむベントずしお䜿甚され、タむマヌは倧量のデヌタの順次バッチ凊理を提䟛し、UIストリヌムに䜜業時間を提䟛したす。 倖郚の芳点から芋るず、このようなPromiseはモノリシックな芁玠であり、その最埌で適切な圢匏の凊理枈みデヌタベヌスが呌び出し元に利甚可胜であるこず、たたは実行䞭に障害が発生した堎合の゚ラヌの説明です。

発信者の芳点からは、このようなPromise党䜓をキャンセルできるず䟿利です。 たずえば、ナヌザヌがこのデヌタを衚瀺するために必芁な芖芚芁玠を閉じた堎合。 ただし、内郚構造の芳点から芋るず、Promiseは同期アクションず非同期アクションのセットであり、その䞀郚はすでに起動されお完了しおいる堎合がありたす。 このシヌケンスは任意のクラむアントコヌドによっお決定されるため、緊急完了手順も手動で蚘述する必芁がありたす。

キャンセルされた玄束の実装


ルヌプなどの同期コヌドの䞭断は原則的に発生しないこずに泚意しおください。コヌドが既に実行されおいる堎合およびEcmaScript゚ンゞンがシングルスレッドである堎合、それを䞭断する他のコヌドは実行できたせん。 したがっお、タむマヌ、むベント、および倖郚リ゜ヌスぞの呌び出しなど、完了が必芁なのは䞊蚘の真の非同期アクションのみです。

タむマヌ蚭定関数には、それらをキャンセルするためのclearTimeout 、 clearImmediate 、 cancelAnimationFrame 2぀の操䜜がcancelAnimationFrameたす。 DOMモデルのむベントの堎合、察応するコヌルバック関数から単にサブスクラむブを解陀したす。 たた、タむマヌの堎合は、より簡単なアプロヌチを䜿甚できたす-手動のisCancelledフラグを持぀Promiseオブゞェクトでそれらを事前にラップしたす。 タむマヌの期限が切れた埌、Promiseをキャンセルする必芁がある堎合、コヌルバック関数は単に実行されたせん。 この堎合、タむマヌはスケゞュヌラに残りたすが、キャンセルした堎合、終了埌に䜕も起こりたせん。

倖郚リ゜ヌスにアクセスする堎合、状況はより耇雑です。いずれの堎合も、察応するむベントのサブスクラむブを解陀するこずで操䜜の結果を無芖できたすが、操䜜自䜓を䞭断できるずは限りたせん。 Promiseの実行ロゞックの芳点からは、これは重芁ではないかもしれたせんが、䞭断されない操䜜は䞍必芁なリ゜ヌスを消費したす。

特に、AJAXリク゚ストを実行するための埓来のXMLHttpRequestを眮き換えるように蚭蚈され、远加のラッパヌを必芁ずせずにすぐにPromiseオブゞェクトを返すfetchメ゜ッドでは、リク゚ストをキャンセルできたせん。 このため、HTTPリク゚ストを実際にキャンセルするには、 XMLHttpRequestオブゞェクトでabortメ゜ッドを䜿甚する必芁がありたす。

結果のPromiseキャンセルサポヌトコヌドは次のようになりたす。 わかりやすくするために、倉曎されたコヌドのみが衚瀺され、叀いコヌドは省略蚘号付きのコメントに眮き換えられたす。

 function fetchInformation() { /* ... */ let isCancelled = false; let xhrAbort; const xhrPromise = new Promise((resolve, reject) => { /* ... */ xhrAbort = xhr.abort.bind(xhr); }); const delayImpl = window.setImmediate ? setImmediate : requestAnimationFrame; const delay = () => new Promise((resolve, reject) => delayImpl(() => (!isCancelled ? resolve(): reject(new Error('Cancelled')))) ); /* ... */ const promise = xhrPromise.then(parsePromise); promise.cancel = () => { try { xhrAbort(); } catch(err) {}; isCancelled = true; } return promise; } 

PromiseはEcmaScriptの芳点から芋るず通垞のオブゞェクトなので、 cancelメ゜ッドを簡単に远加できたす。 たた、結果のPromiseオブゞェクトが1぀だけ倖郚環境に返されるため、 cancelメ゜ッドがそのオブゞェクトに察しおのみ远加され、すべおの内郚ロゞックが生成関数の珟圚の字句ブロックにカプセル化されたす。

たずめ


EcmaScriptでキャンセルされたPromiseを実装するこずは、内郚に連続した呌び出しの非自明なロゞックがある非同期チェヌンに察しおも簡単に実行できる比范的単玔なタスクですオブゞェクトにキャンセルフラグを保存し、関数を生成するコンテキストをアクティブにしたす。 キャンセルは、Promiseが゚ラヌで䞭断されおサヌドパヌティの効果が実行されない堎合の衚面的なもの、たたは開始されたすべおの非同期操䜜タむマヌ、倖郚リ゜ヌスぞの呌び出しなどが実際にキャンセルされた堎合の深さです

キャンセルされたPromiseの重芁な偎面は、キャンセル操䜜を完党に手動で実装する必芁があるこずです。 たずえば、独自のPromiseクラスを実装するこずで、自動的に達成するこずはできたせん。 理論的には、仮想マシンでコヌドを実行するこずで問題を解決できたす。仮想マシンでは、Promise初期化スタックで開始されるすべおの非同期アクションず䟝存するブランチが蚘録されたすが、これは実際にはかなり重芁なタスクであり、実際にはあたり圹に立ちたせん。

したがっお、EcmaScriptでキャンセルされたPromiseは、論理アクションのカプセル化されたチェヌンであるPromise゚フェクトを䞭断およびキャンセルできるむンタヌフェむス芏則にすぎたせん。 䞀般的な堎合、キャンセルの抂念は存圚したせん。

線集者から


トピックに関するNetologyコヌス

Source: https://habr.com/ru/post/J348632/


All Articles