非同期コードのデバッグを停止し、開始する方法

アンドレイ・サロマチン( filipovskii_off


アドレナリン・サロマチーン

今日、毎日新しいプログラミング言語(Go、Rust、CoffeeScript)があります。 私も自分のプログラミング言語を考え出したいと決めました。世界には新しい言語が欠けていました...

ご列席の皆様、今日はSchlecht!スクリプトをご紹介します!スクリプトはクレイジーなプログラミング言語です。 すぐに使用を開始する必要があります。 それは私たちが慣れているすべてのものを持っています-それは条件演算子を持ち、サイクルがあり、関数と高次の関数があります。 一般的に、通常のプログラミング言語に必要なものはすべて揃っています。

あまり一般的ではなく、一見しただけで押しのけられることもありますが、Schlecht!スクリプト関数には色が付いています。



つまり、関数を宣言するとき、呼び出すときに、その色を明示的に指定します。

機能は、赤と青の2色です。

重要なポイント:青い関数内では、他の青い関数のみを呼び出すことができます。 青い関数内で赤い関数を呼び出すことはできません。



赤の関数内では、赤と青の両方の関数を呼び出すことができます。



私はそうすべきだと決めました。 すべての言語はそのようでなければなりません。

微妙な点:赤い関数を書いて呼び出すことは痛いです! 「痛い」と言うとき、どういう意味ですか? 実際、私は今ドイツ語を勉強しているので、赤い関数はすべてドイツ語で呼び出すべきだと決めました。



これはドイツ語で関数を書く方法です:



「!」は必須です-結局、ドイツ語で書いています。

そのような言語で書くには? 2つの方法があります。 青色の関数のみを使用できますが、その中に書き込むのは苦痛ではありませんが、内部では赤色の関数は使用できません。 このアプローチはうまくいきません。インスピレーションを得て、赤い関数に関する標準ライブラリの半分を書いたので、すみません...

あなたへの質問-そのような言語を使用しますか? Schlecht!Scriptを販売しましたか?

まあ、あなたは、いわば、選択の余地がありません。 ごめんなさい...


Javascript

JavaScriptは素晴らしい言語です。私たちは皆、それを愛しています。JavaScriptが大好きなので、私たちは皆ここにいます。 しかし、問題はJavaScriptがSchlecht!Scriptの機能の一部を継承していることであり、自慢したくありませんが、私のアイデアをいくつか盗んだと思います。

彼らは正確に何を継承しますか? JavaScriptには赤と青の機能があります。 JavaScriptの赤の関数は非同期関数、青は同期関数です。 そして、すべてが同じチェーンでトレースできます...赤の関数は、Schlecht!スクリプトで呼び出すのは苦痛であり、非同期関数はJavaScriptで呼び出すのは苦痛です。

そして、青い関数の中に赤い関数を書くことはできません。 これについては後で詳しく説明します。



なぜ痛いのですか? 非同期関数を呼び出して記述するとき、どこから痛みが生じますか?

条件演算子、ループ、リターンの動作は異なります。 try / catchは機能しません。非同期関数は抽象化を壊します。

各項目についてもう少し。



shouldProcessとprocessが同期関数であり、条件付き演算子が機能する同期コードは次のようになります。一般的には、すべてが問題ありません。

同じですが、非同期の場合は次のようになります。



再帰が現れ、状態をパラメーター、関数に渡します。 一般的に、見ることは本当に不快です。 try / catchは機能しません。また、try / catchでコードの非同期ブロックをラップすると、例外をキャッチしないことはわかっています。 コールバックを渡し、イベントハンドラを再配置する必要があります。一般的に、try / catchはありません...

そして、非同期関数は抽象化を壊します。 どういう意味ですか? キャッシュを書いたと想像してください。 メモリ内にユーザーキャッシュを作成しました。 そして、このキャッシュから読み取る関数があります。これは、すべてがメモリ内にあるため、当然同期的です。 明日、数千、数百、数十億のユーザーが来ます。このキャッシュをRedisに配置する必要があります。 キャッシュをRedisに配置すると、関数は非同期になります。これは、Redisにより非同期でしか読み取れないためです。 そして、それに応じて、スタック全体が非同期になるため、同期関数を呼び出したスタック全体を書き換える必要があります。 一部の機能がキャッシュからの読み取り機能に依存している場合、これも非同期になります。

一般的に、JavaScriptの非同期性について言えば、そこではすべてが悲しいと言えます。

しかし、私たちは非同期性について何をしているのでしょうか? 最後に、私について少し話しましょう。



みんなを救うために来ました。 さて、私はそれをやろうとします。

私の名前はアンドレイです。ベルリンの生産的なモバイルのスタートアップで働いています。 私はモスクワJSの組織を支援し、RadioJSの共同ホストです。 JavaScriptだけでなく、非同期性のトピックにも非常に興味があります。原則として、これが言語の決定的な瞬間だと思います。 言語が非同期で機能する方法によって、その成功と、人々が言語を喜んで快適に使用する方法が決まります。

特にJavaScriptの非同期性について言えば、常に相互作用する2つのシナリオがあるように思えます。 これは、複数のイベントを処理し、単一の非同期操作を処理しています。

多くのイベント(たとえば、DOMイベントやサーバーへの接続)は、多くの種類のイベントを発生させるものです。

単一の操作は、たとえば、データベースからの読み取りです。 単一の非同期操作は、1つの結果またはエラーを返します。 これ以上のオプションはありません。

そして、これらの2つのシナリオについて言えば、推測するのは興味深いです。ここでは、非同期性が悪い、一般的にはすべてが悲しい...でも、私たちは本当に何が欲しいのでしょうか? 完璧な非同期コードはどのようなものでしょうか?



そして、私たちは、制御の流れを制御したいと思っています。 条件付きステートメント、ループは、非同期コードと同じように同期コードでも機能するようにします。

例外処理が必要です。 非同期操作で使用できない場合、なぜtry / catchが必要ですか? これはただ奇妙です。

そして、もちろん、単一のインターフェースを持つことが望ましいです。 非同期関数を同期関数とは異なる方法で記述して呼び出す必要があるのはなぜですか? すべきではありません。

それが私たちの望みです。

今日、私たちには何があり、将来どのようなツールが登場しますか?



ECMAScript 6について話している場合(原則として、これが今日説明する内容です)、多くのイベントを処理するためのEventEmitterとStream、Continuation Passing Style(単一の非同期操作を処理するためのコールバック)があります。 i)、約束とコルーチン。



ECMAScript 7には、多くのイベントを処理するための非同期ジェネレーターと、単一の非同期操作を処理するためのAsync / Awaitがあります。

これについてお話します。

まず、ECMAScript 6にあるのは、多くの非同期イベントを処理することです。 たとえば、マウスイベントやキーストロークの処理を思い出させてください。 Node.jsのブラウザーに実装されているEventEmitterパターンがあります。 多くのイベントを処理するほとんどすべてのAPIにあります。 EventEmitterは、イベントを発行し、各タイプのイベントにハンドラーをアタッチするオブジェクトを作成できることを示しています。



インターフェイスは非常にシンプルです。 EventListenerを追加し、イベントの名前でEventListenerを削除し、そこにコールバックを渡します。



たとえば、XMLHttpRequestで、多くのイベントについて話すとき、多くの進行イベントを持つことができることを意味します。 つまり AJAXリクエストを使用して一部のデータをロードすると、進行イベントが発生し、ロード、中止、エラーイベントが1回発生します。



エラーは、ユーザーにエラーを通知するための特別なイベント、EventEmittersおよびStreamsのユニバーサルイベントです。

多くの実装があります:



ここにリストされているのはほんの一部であり、レポートの最後に、これらすべての実装がある場所へのリンクがあります。

Node.jsでは、EventEmitterはデフォルトで組み込まれていると言うことが重要です。

そのため、これはNode.jsのAPIとブラウザにほぼ標準で備わっているものです。

多くのイベントに対処するには他に何が必要ですか? ストリーム

ストリームはデータストリームです。 データとは? たとえば、ファイルのデータ、テキストデータ、またはオブジェクトやイベントなどのバイナリデータです。 最も一般的な例:



ストリームにはいくつかのタイプがあります。



ここでは、スタイラスファイルからcssファイルへの変換のチェーンを見て、自動プレフィックスを追加します。これは、Andrei Sitnikと彼の自動プレフィックスが大好きだからです。

いくつかのタイプのストリームがあることがわかります。ソースストリームはgulp.srcであり、ファイルを読み取り、ファイルオブジェクトを生成し、変換ストリームに進みます。 最初の変換ストリームはスタイラスからcssファイルを作成し、2番目の変換ストリームはプレフィックスを追加します。 そして最後のタイプのストリームはコンシューマーストリームで、これらのオブジェクトを受け取り、ディスクのどこかに何かを書き込み、何も出力しません。



つまり データソース、変換、コンシューマの3種類のフローがあります。 そして、これらのパターンは、gulpだけでなく、DOMイベントを観察するときにもどこでもトレースできます。 それらを変換するDOMイベントを発行するスレッドがあり、これらのDOMイベントを消費すると特定の結果が返されます。

これは、コンベアと呼ばれるものです。 スレッドの助けを借りて、オブジェクトがパイプラインの最初のどこかに配置され、変換のチェーンを通過し、人々がそれに到達すると、何かを変更し、追加し、削除し、最終的に何かを得るときに、このようなチェーンを構築できます車。

いくつかのストリーム実装があります。または、それらはObservableです。



Node.jsでは、ストリームは組み込みです-これらはノードストリームです。

したがって、EventEmitterとStreamがあります。 EventEmitterは、すべてのAPIでもデフォルトで設定されています。 ストリームは、複数のイベントを処理するためのインターフェイスを統合するために使用できるアドオンです。



非同期APIを比較する基準について説明すると、概して、returnステートメント、loopステートメントは機能しません。try/ catchは機能しません。もちろん、同期操作を行う単一のインターフェースとはほど遠いです。 。

一般に、ECMAScript 6で多くのイベントを処理するには、すべてがあまり良くありません。

単一の非同期操作について話すとき、ECMAScript 6には3つのアプローチがあります。



継続受渡しスタイルは、コールバックでもあります。



みなさんはそれに慣れていると思います。 これは、非同期リクエストを作成し、そこにコールバックを渡すときです。コールバックは、エラーまたは結果とともに呼び出されます。 これは一般的なアプローチであり、Nodeのブラウザにもあります。



このアプローチの問題は、あなたもすべてを理解していると思います。



これは、すべての機能が同期である場合、ユーザーのツイートフィードを非同期で受信する方法です。



同じコードですが、同期的には次のようになります。



フォントを増やして、見やすくすることができます。 そして、もう少し増やしてください...そして、これがSchlecht!Scriptであることをよく認識しています。



彼らは私の考えを盗んだと言った。

Continuation Passing StyleはNode.jsの標準ブラウザAPIであり、私たちはそれらを常に使用していますが、不便です。 したがって、約束があります。 これは、非同期操作であるオブジェクトです。



Promiseは、将来、非同期的に何かをするという約束のようなものです。



thenメソッドを使用してコールバックを非同期操作にアタッチできます。これは、原則として、APIのメインメソッドです。 そして、非常に重要なことに、Promiseをチェーンできます。その後、順番に呼び出すことができ、その後に渡されるすべての関数もPromiseを返すことができます。 これは、PromisesのTwitterからのユーザーフィードリクエストの外観です。

もちろん、このアプローチをContinuation Passing Styleと比較すると、Promisesの方が使いやすいことは言うまでもありません。定型文を書く機会が少なくなります。

Continuation Passing Styleは、Node.js、io.jsのすべてのAPIで引き続きデフォルトで使用されており、いくつかの理由でPromisesに切り替えることすら計画していません。 最初は、多くの人が理由はパフォーマンスだと言っていました。 実際、2013年の調査では、約束はコールバックよりもはるかに遅れていることが示されています。 しかし、bluebirdなどのライブラリの出現により、bluebirdのPromiseのパフォーマンスはコールバックに近いため、そうではないと自信を持って言えます。 重要なポイント:なぜPromisesがこれまでAPIでの使用を推奨されないのですか? APIからPromiseを発行すると、実装が強制されるためです。

すべてのPromisesライブラリは標準に従う必要がありますが、Promisesを発行するときは、実装も発行します。 遅いPromiseを使用してコードを記述し、APIから遅いPromiseを発行する場合、ユーザーにとってあまり快適ではありません。 したがって、外部APIについては、もちろん、コールバックの使用を推奨しています。



たくさんのPromises実装があり、実装を書いていないなら、あなたは本当のJavaScriptプログラマーではありません。 Promiseの実装を作成しなかったため、自分の言語を考え出す必要がありました。

そのため、一般に、Promisesは定型文よりわずかに小さいですが、それでもまだそれほど良くありません。

コルーチンはどうですか? 興味深いものはすでにここから始まっています。 想像してみて...

これは面白いです。 私たちはブダペストのJSConfにいましたが、JavaScriptなどでクアドロコプターをプログラムしたクレイジーな人がいて、彼が見せようとしたことの半分は成功しませんでした。 したがって、彼は絶えず言いました:「OK、今想像してください...このクアッドコプターが離陸し、すべてがうまくいった...」。

実行のある時点で関数を一時停止できると想像してください。



ここで、関数はユーザー名を受け取り、データベースにクロールし、ユーザーオブジェクトを受け取り、名前を返します。 当然、「データベースにアクセス」-getUser関数は非同期です。 getUserが呼び出されたときにgetUserName関数を一時停止できたらどうでしょうか? ここで、getUserName関数を実行し、getUserにアクセスして停止しました。 getUserはデータベースにアクセスし、オブジェクトを受け取り、それを関数に返し、実行を続けます。 それはどれほどクールでしょう。

実際、コルーチンは私たちにこの機会を与えてくれます。 コルーチンは、いつでも一時停止および再開できる機能です。 重要なポイント:プログラム全体を停止しません。



これはブロッキング操作ではありません。 特定の場所で特定の機能の実行を停止します。



javascriptジェネレーターを使用すると、getUserNameはどのようになりますか? 関数がジェネレーターを返すことを示すために、関数宣言に「*」を追加する必要があります。 関数を一時停止する場所でyieldキーワードを使用できます。 そして、ここでgetUserがPromiseを返すことを覚えておくことが重要です。

なぜなら ジェネレーターはもともとJavaScriptで遅延シーケンスを実行するために発明されたもので、概して、それらを同期コードに使用するのはハックです。 したがって、何らかの形でこれを補うライブラリが必要です。



ここでは、「co」を使用してジェネレーターをラップし、非同期関数を返します。

合計、以下が得られます:



if、for、その他の演算子を使用できる関数があります。

値を返すには、同期関数の場合と同様に、単にreturnを記述します。 内部でtry / catchを使用でき、例外をキャッチします。



getUserを使用したPromisesがエラーで解決した場合、これは例外としてスローされます。

getUserName関数はPromiseを返すため、Promiseと同じように操作できます。then、chainなどを使用してコールバックを切断できます。

しかし、私が言ったように、非同期コードにジェネレーターを使用することはハックです。 したがって、外部APIとして公開することは望ましくありません。 ただし、アプリケーション内での使用は問題ないので、コードを転置できる場合は使用してください。



多くの実装があります。 すでに標準の一部であるジェネレーターを使用しているものもありますが、Node.jsで動作し、ジェネレーターを使用しないファイバーもあり、独自の問題があります。

一般に、これは単一の非同期操作を操作するための3番目のアプローチであり、これは依然としてハックですが、同期に近いコードを既に使用できます。 条件ステートメント、ループ、およびtry / catchブロックを使用できます。



つまり 単一の非同期操作で動作するECMAScript 6は、目的の結果に少し近づいているように見えますが、特別な「*」を記述してキー演算子「yield」を使用する必要があるため、コルーチンでも、単一のインターフェイスの問題は解決されません。



そのため、ECMAScript 6では、多くのイベントを処理するために、単一の非同期操作(CPS、Promise、Coroutines)を処理​​するEventEmitterとStreamがあります。 そして、これはすべて素晴らしいようですが、何かが欠けています。 もっと勇気があり、大胆で、新しいものが欲しい、革命が欲しい。

そして、ES7を書いた人たちは私たちに革命を起こすことに決め、Async / AwaitとAsync Generatorsをもたらしました。



Async / Awaitは、たとえばデータベースクエリなどの単一の非同期操作で作業できるようにする標準です。

これが、ジェネレーターでgetUserNameを作成した方法です。



そして、これは同じコードがAsync / Awaitでどのように見えるかです:



すべてが非常によく似ており、概して、これはハックから標準への一歩です。 ここにキーワード「async」があります。これは、関数が非同期であり、Promiseを返すことを示しています。 非同期関数内では、Promiseを返すawaitキーワードを使用できます。 そして、このPromiseが完了するのを待つことができます。関数を一時停止して、このPromiseが完了するのを待つことができます。



また、条件文、ループ、try / catchの機能もあります。つまり、非同期関数はES7で合法化されます。 ここで、関数が非同期の場合、キーワード「async」を追加すると明示的に言います。 そして、これは原則としてそれほど悪くはありませんが、ここでも単一のインターフェースはありません。

たくさんのイベントはどうですか? ここには、非同期ジェネレーターと呼ばれる標準があります。

一般的に、多数とは何ですか? JavaScriptで多数をどのように使用しますか?



多くのことをループで処理するので、ループで多くのイベントを処理しましょう。



非同期関数内では、... on構文のキーを使用できます。これにより、非同期コレクションを反復処理できます。 のような。

この例では、observeは反復可能なもの、つまり ユーザーがマウスを動かすたびに、mousemoveイベントが発生します。 このサイクルに陥り、何らかの形でこのイベントを処理します。 この場合、画面にダッシュを描画します。



なぜなら 関数は非同期です。Promiseを返すことを理解することが重要です。 しかし、たとえば、何らかの方法でWebソケットからのメッセージを処理したり、それらをフィルタリングしたりする場合に、多くの値を返したい場合はどうでしょうか。 つまり たくさんあり、出口にはたくさんあります。 ここでは非同期ジェネレータが役立ちます。 「非同期関数*」と記述し、関数が非同期であると言い、何らかのセットを返します。



この場合、WebソケットのMessageイベントを調べて、発生するたびに何らかの検証を行い、検証に合格すると、返されたコレクションに戻ります。 このメッセージを追加する方法。



さらに、これはすべて非同期に行われます。 メッセージは蓄積されず、到着すると返されます。 そして、ここですべての条件ステートメント、ループ、およびtry / catchも機能します。

質問:filterWSMessagesは何を返しますか?



それは間違いなくPromiseではありません、それはある種のコレクションだからです、そのようなもの…しかしそれは配列でもありません。



さらに。 イベントを生成するこれらの観察は何を返しますか?

そして、彼らはいわゆる Observablesオブジェクト。 これは新しい言葉ですが、概して、Observableはストリームであり、これらはストリームです。 したがって、円は閉じます。

合計で、非同期の単一操作を操作するためのAsync / Await、多くの操作を行うためのAsync Generatorがあります。

私たちが残したものとどこから来たのかを少し振り返ってみましょう。

ツイートフィードを取得するには、CPSで次のコードを記述します。



多くの定型文、エラー処理、実際のマニュアル、そして一般的にはあまり快適ではありません。

Promiseでは、コードは次のようになります。



より小さなボイラープレートで、1か所で例外を処理できます。これは既に適切ですが、それでもこれらは存在します... try / catchも条件演算子も機能しません。

Async / Awaitを使用すると、次の構造が得られます。



そして、ほぼ同じように、彼らは私たちにコルーチンを与えます。

ここでは、この関数を「非同期」として宣言する必要があることを除いて、すべてが素晴らしいです。

イベントのセットに関して、DOMイベントについて話している場合、これはmousemoveを処理し、EventEmitterを使用して画面に描画する方法です。



, Stream' Kefir :



mousemove window, - , callback . , end stream', DOM,

Async Generators :



, - .

-, .

, , , .


, Schlecht!Script, GitHub .., , - , — , , , , , - , , , , Promises.

連絡先


» twitter
» filipovskii_off

— - FrontendConf . 2017 .

Frontend Conf , , . , 8 , .

HighLoad++ 11 « », :

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


All Articles