非同期JavaScript:コールバックまたはプロミスなし

おそらく、JavaScriptを使用したすべての人が非同期呼び出しに遭遇した(または将来遭遇する)ことでしょう。 たぶん、これはサーバー側のデータベースへの呼び出しでしょう。 たぶん-タイマーを使ってサイトでアニメーションを作成します。



非同期性を「克服」するために、約束からプログラミング言語の変更まで、さまざまなツールが使用されます。 しかし、私は本当にすべてをドロップして、純粋なJSで線形コードを書きたい場合があります。



timeout(1000); console.log('Hello, world!');
      
      







このようなものを実装することは可能ですか? もちろんできます。

この記事では、危険ではあるが効果的な方法を検討します。



アクションオプション



コルベックに不満がある場合は、いくつかの開発パスを見つけることができます。



もちろん、最初と2番目のオプションは考慮せず、それらの実装は読者の良心に任せます。 3番目のオプションはより興味深いものです。つまり、私たちはJSで書いているわけではありませんが、すべてにかかわらず、単純な期待に反して、出力はそのコードです。 これは、「JSを拡張し、そこに非同期演算子を追加して、AJSを呼び出しますか?」

このようなソリューションを実装すると、不要なエンティティ、つまり新しい言語のコンパイラが追加されます。 この場合、著者は自分の新しい革新的な製品を十分に公表し、一般の人々に別のコンパイラをインストールするよう強制し、開発プロセスに別の明示的なコード変換を導入する必要があります。 著者が大企業の利益を代表せず、彼の時代の認められた権威ではない場合、何も行われません。



しかし、待って、あなたはいつでも自分の髪を引っ張ることができます! 動的言語を使用すると、外出先で何でもコンパイルできます。残りは、プログラマにとって便利な書き方を決定することだけです。



記録形式の選択



最初の近似として、文字列リテラルを使用して非同期コードを記述し、それを処理し、evalを呼び出すことができます。 確かに、このかさばりは、コールバックのスタックとそれほど変わりません。 少し考えれば、関数内でコメントを使用できます。 関数に適用されたtoStringメソッドは、ソースコードを文字列として返します。 コメント内に複数行の行を実装しても、誰も驚かないでしょう。 作成者の希望に応じて、コードを数行追加すると、先頭のスペースや改行が削除されるか、削除されません。 たとえば、このテクノロジーを使用すると、コメント付きの複数行の正規表現や、Brainfuck やJavaScript自体などの言語のインタープリターを実装できますが、あと2、3行追加するだけです

複数行の正規表現
 function createRegExp(func){ if(typeof func !== 'function') throw new TypeError(func + ' is not a function'); var m = func.toString(). replace(/\s+|--.*?((?=\*\/)|$)/gm, ''). //       /* */,    -- match(/^.*?\/\*\/((?:\\\/|.)*?)\/(?:([img]+?)\/?)?\*\/\}$/); //    if(!m) throw new TypeError('Invalid RegExp format'); return new RegExp(m[1], m[2] || undefined); }
      
      







そして、例として-複数行の正規表現のコメント内の正規表現の分析のための正規表現の分析:



 var re = createRegExp(function(){/* / ^.*?\/\* -- -        \/ --  "/" -    ( (?: \\\/ | . )*? )--     "\/"   , \/ --    "/" (?:([img]+?)\/?)? --      i, m, g, ,   "/", --   \*\/\}$ --        --     / */});
      
      







「-」文字シーケンスは、ハイフンをスペースで区切ることにより、正規表現で引き続き使用できることに注意してください。





このようなトリックは、文字列を操作するときに役立ちますが、構文の強調表示を完全に無効にし、Javaのソースコードにとって非常に重要です。 したがって、コードをコメントアウトして使用するのではなく、機能させる必要があります。 つまり、少なくとも分解してASTの形式で表示できるものです。そうしないと、インタープリターエラーが発生します。



使用できる構造


方言を実現するために、言語のフレームワーク内に留まりながら、次を使用できます。







これにより、関数のソースコードを受け取った後、「大事にされた」デザインが使用されている場所を見つけ、promises / nested functionsの使用に置き換え、非同期呼び出しの不正使用に関する苦情を送信できます。

各デザインには長所と短所があります。 たとえば、未定義の変数に関する警告やエラー、静的アナライザーからの警告などを取得できます。 しかし、最終的に、何を使用するかは、開発者が自分で決定します。



実装



たとえば、図に示すオプションを選択します。







元の関数に__cb引数を追加することにより、最後の非同期操作が完了した後にコントロールが渡す関数。 非同期呼び出しは矢印(<-)で示され、関数の結果を右側の変数に左側の変数に入れるとよいことを示します。 すべての矢印は、ネストされたコールバックの呼び出しの生成に置き換えられます。 後続のコードはすべて「パック」されます。 関数からの各戻り値は、__ cbコールバックへの呼び出しを伴う戻り値に置き換えられます。



これにより、非同期関数を呼び出し、制御を別の関数に転送し、作成されたすべての変数を使用できます(矢印の前の新しい変数はそれぞれ、後続のコードまたはその親コン​​テキストのレキシカルコンテキストにあります)。 矢印は、おなじみの「<」および「-」演算子のシーケンスであり、2つの数値を比較する有効な式を形成します。



簡単にするために、実行コンテキストを伝えることはできませんが、矢印があるため、アプローチの機能をすでに実証している、ソースコード変換の簡略化された実装を検討してください。



 function async(func){ //    "function..." (prefix)   (code) var parsed = func.toString() .match(/(function.*?\{)([\s\S]*)\}.*/); var prefix = parsed[1], code = parsed[2]; //  lines       ,  "<-" //  nends     -   //     //        var lines = ['(' + prefix], nends = 2; //   ... (    \n) code.split('\n').forEach(function(line){ // ... ,     "<-", //   -     if(!/<-/.test(line)) return void lines.push(line, '\n'); //   -          , //         lines var parsed = /([\w\d_$\s,]+)<-(.+)\)/.exec(line); lines.push(parsed[2], ', function(', parsed[1], '){\n'); ++nends; //       }); //        return lines.join('') + Array(nends).join('\n});'); }
      
      







見た目と作成された機能のために非常に簡単に書かれています!



興味のある方は、請求された変換を説明するより詳細なバージョンを参照できます。
 function async(func){ if(typeof func != 'function') throw new TypeError('First argument of "async" must be a function.'); //  ,   "function...",    var parsed = func.toString() .replace(/\/\*[\s\S]*?\*\/|\/\/.*$/mg, '') .match(/(function.*?)\((.*?)\)\s*\{([\s\S]*)\}.*/); var prefix = parsed[1], args = parsed[2], code = parsed[3]; //   ,     __cb if(!/^\s*$/.test(args)) args += ','; //      "})" //        , ends  //    var lines = ['(', prefix, '(', args, '__cb', '){'], ends = ['\n})']; code.split('\n').forEach(function(line){ //        line = line.replace(/return\s*(.*?);/, 'return void __cb($1);'); // ,   "<-"    if(!/<-/.test(line)) return void lines.push(line, '\n'); if(/<-.*?<-/.test(line)) throw new Error('"<-" is found more than 1 times in "'+line+'".'); //       var parsed = /([\w\d_$\s,]+)<-(.+)\((.*)\)/.exec(line); if(!parsed) throw new Error('"<-" is used incorrectly in "' + line + '".'); lines.push(parsed[2], '('); if(parsed[3]) lines.push(parsed[3], ', '); lines.push('function(', parsed[1], '){\n'); ends.push('\n});'); }); //     "})" return lines.concat(ends.reverse()).join(''); }
      
      









使用する



あまりにも分散したシステムでアバターを取得するには、サーバーがメールで1つのデータベースにアクセスし、そこからユーザーIDを取得し、2番目のデータベースから識別子を使用してユーザーデータ(ファイルパスを含む)を取得し、ファイルシステムにアクセスする必要があるとしますファイルをアップロードします。







単純なソリューションは次のようになります。

 function getAvatarData(eMail, callback){ db1.getID(eMail, function(id){ db2.getData(id, function(user){ fs.exists(user.avatarPath, function(exists){ if(!exists) return void callback(null); fs.readFile(user.avatarPath, callback); }); }); }); }
      
      







そして、矢印を使用すると、より「平ら」にできます。

 function getAvatarData_src (eMail) { id <- db1.getID(eMail); user <- db2.getData(id); exists <- fs.exists(user.avatarPath); if (!exists) return null; data <- fs.readFile(user.avatarPath); return data; } var getAvatarData = eval(async(getAvatarData_src));
      
      







結果



JavaScriptで構文糖衣を非常に簡単に実装できることがわかりました。 構文の強調表示とオートコンプリートを維持し、外部プリプロセッサと多くのコールバックを取り除きました。 おそらく彼らはプロトタイプ作成ツールを手に入れたのでしょう。

もちろん、理想的なケースでは、構文上の驚きから身を守り、すべての状況を正しく処理するために、正規表現ではなく、通常のJSパーサーを使用する必要があります(数十行で機能を追加するという事実だけで十分ですが)。



構成「a <-f()」を最適化または別の形式に変換できるため、コードの最小化を拒否する必要がある場合があります。 また、コンテキストに注意する必要があります。 注意深い読者が指摘したように、上記の関数は既製の関数ではなく文字列を返します。 この動作が選択された理由は、非同期とそのコードが異なるファイルに分離される可能性があるためです。この場合、evalは関数の目的の字句コンテキストを「キャプチャ」しません。 evalは、ユーザーコードと同じファイルで呼び出す必要があります。



また、提示された実装は例外で正しく動作しませんが、その処理は簡単に実装できます。 そしてもちろん、それは真面目な上司や超大企業プロジェクトと対立します。



したがって、これは自宅でのみ繰り返してください!



All Articles