ES2017のスタイルでKoaJS 2のマイクロサービスを作成しています。 パートI:そのような異なる非同期性

コアv2



すべてをゼロから書き直し、互換性のために「スコア」を書き、すべてを「賢く」やりたいと思ったことはありませんか? KoaJSがそのように作成された可能性が高いです。 このフレームワークは、Expressチームによって数年間開発されました。 これらの2つのフレームワークに関する専門家は次のように書いています。



Koaは、最新のES6(ES2015)の世界に没頭している最初の行から、レガシーコードサポートの負担を負いません。バージョン2には、将来のES2017標準の設計がすでにあります。 私の会社では、このフレームワークは2年間稼働しており、プロジェクトの1つ( AUTO.RIA )は1日あたり50万人の訪問者の負荷に取り組んでいます。 このフレームワークは、現代/実験標準に偏っていますが、ExpressやCallBackスタイルのアプローチを備えた他の多くのフレームワークよりも安定しています。 これは、フレームワーク自体によるものではなく、その中で使用されている最新のJSデザインによるものです。



この記事では、コアの開発経験を共有したいと思います。 最初の部分では、フレームワーク自体とその上にコードを整理するための小さな理論を説明します。2番目では、koa2に小さな休憩サービスを作成し、既に踏み込んだすべてのレーキをバイパスします。



理論のビット



JSONファイルからオブジェクトにデータを読み込む関数を作成する簡単な例を見てみましょう。 明確にするために、「reqiure( 'my.json')」なしで行います。

const fs = require('fs'); function readJSONSync(filename) { return JSON.parse(fs.readFileSync(filename, 'utf8')) } //... try { console.log(readJSONSync('my.json')); } catch (e) { console.log(e); }
      
      







readJSONSyncを呼び出すときにどんな問題が発生しても 、この例外を処理します。 ここではすべて問題ありませんが、大きな明らかなマイナス点があります。この関数は同期的に実行され、読み取りの全期間スレッドをブロックします。



コールバック関数を使用して、nodejsスタイルでこの問題を解決してみましょう。

 const fs = require('fs'); function readJSON(filename, callback) { fs.readFile(filename, 'utf8', function (err, res) { if (err) return callback(err); try { res = JSON.parse(res); callback(null, res); } catch (ex) { callback(ex); } }) } //... readJSON('my.json', function (err, res) { if (err) { console.log(err); } else { console.log(res); } })
      
      







非同期性はすべて良好ですが、コードの使いやすさが低下しています。 エラー 'if(err)return callback(err)'のチェックを忘れる可能性がまだあり、ファイルの読み取り中に例外が発生した場合、すべてが失敗します。2番目の不便は、すでに一歩踏み込んでいるということです。コールバック、地獄。 非同期関数が多数ある場合、ネストが拡大し、コードが非常に読みにくくなります。



さて、この問題をより現代的な方法で解決してみましょう。readJSON関数を約束し ましょう



 const fs = require('fs'); function readJSON(filename) { return new Promise(function(resolve,reject) { fs.readFile(filename,'utf8', function (err, res) { if (err) reject(err); try { res = JSON.parse(res); resolve(res); } catch (e) { reject(e); } }) }) } //... readJSON('my.json').then(function (res) { console.log(res); }, function(err) { console.log(err); });
      
      







このアプローチは、少し進歩的です。 大きく複雑なネストをチェーンに「拡張」することができます...そして...それから、次のようになります:

 readJSON('my.json') .then(function (res) { console.log(res); return readJSON('my2.json') }).then(function (res) { console.log(res); }).catch(function (err) { console.log(err); } );
      
      







これまでのところ、この状況は大幅に変化することはなく、コードの美しさに見た目が改善されており、何が行われているかがより明確になっている可能性があります。 koa v1エンジンの基礎となったジェネレーターcoライブラリの出現は、状況を根本的に変えました。

例:

 const fs = require('fs'), co = require('co'); function readJSON(filename) { return function(fn) { fs.readFile(filename,'utf8', function (err, res) { if (err) fn(err); try { res = JSON.parse(res); fn(null,res); } catch (e) { fn(e); } }) } } //... co(function *(){ console.log(yield readJSON('my.json')); }).catch(function(err) { console.log(err); });
      
      







yieldディレクティブが使用される場所では、非同期readJSONが保留中です。 readJSONは少しやり直す必要があります。 このコード設計は、サンク関数と呼ばれます。 nodejsスタイルで記述された関数をthunkifyサンク関数にする特別なライブラリがあります。

これにより何が得られますか? 最も重要なことは、yieldを呼び出す部分のコードが順番に実行されることです。

  console.log(yield readJSON('my.json')); console.log(yield readJSON('my2.json'));
      
      





そして、最初に「my.json」、次に「my2.json」を読み取って順次実行を取得します。 しかし、これは「コールバックさようなら」です。 ここで「ugさ」とは、発電機の仕事の特性を意図した目的ではなく使用することです。サンク関数は非標準のものであり、この「氷ではない」形式でコアのすべてを書き換えます。 すべてがそれほど悪いわけではないため、サンク関数だけでなく、promise、promiseの配列、またはpromiseを持つオブジェクトに対してもyieldを実行できることが判明しました。

例:

  console.log( yield { 'myObj': readJSON('my.json'), 'my2Obj': readJSON('my2.json') } );
      
      







あなたはより良いものを想像できないように見えましたが、彼らはそうしました。 彼らは、すべてが「直接」の目的のためにそれを作りました。 非同期機能を満たす

 import fs from 'fs' function readJSON(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, 'utf8', function (err, res) { if (err) reject(err); try { res = JSON.parse(res); resolve(res) } catch (e) { reject(e) } }) }) } //... (async() => { try { console.log(await readJSON('my.json')) } catch (e) { console.log(e) } })();
      
      







急いで実行しないでください。ノードが理解できないこの構文をバベルなしで実行してください。 Koa 2はこのスタイルで動作します。 まだ逃げていませんか?



この「コールバックキラー」の仕組みを見てみましょう。

 import fs from 'fs'
      
      





同様に

 var fs = require('fs')
      
      







私はすでに約束に精通しています。



()=> {} -これは、表記関数(){}と同様に、「矢印関数」と呼ばれるものです。 矢印関数にはわずかな違いがあります-コンテキスト:これは矢印関数が初期化されるオブジェクトを指します。



関数が非同期であることを示す前にasyncを行うと、そのような関数の結果も約束されます。 この場合、この関数を実行した後、そこで何もする必要がないため、thenまたはcatch呼び出しを省略しました。 次のようになりますが、これも機能します。

 (async() => { console.log(await readJSON('my.json')) })().catch (function(e) { console.log(e) })
      
      







awaitは、非同期関数(約束)の実行を待機し、返された結果を処理するか、例外を処理する必要がある場所です。 ある程度、発電機の歩留まりに似ています。



理論は終わりました-KoaJSの最初の起動を開始できます。



コアを満たす





コアの「Hello world」:

 const Koa = require('koa'); const app = new Koa(); // response app.use(ctx => { ctx.body = 'Hello Koa'; }); app.listen(3000);
      
      







app.useで引数として渡される関数はミドルウェアと呼ばれます。 ミニマリストですね。 この例では、この関数の記録の短縮バージョンが表示されます。 Koaミドルウェアの用語には3つのタイプがあります。





また、コード実行フェーズの観点から、ミドルウェアは、リクエストの処理前(上流)と処理後(下流)の2つのフェーズに分けられます。 これらのフェーズは、ミドルウェアに渡される次の機能によって分離されます。



共通機能



 // Middleware   2  (ctx, next), ctx   , // next        'downstream'  middleware.   ,       then          . app.use((ctx, next) => { const start = new Date(); return next().then(() => { const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }); });
      
      







非同期関数(babel transporterで動作します)



 app.use(async (ctx, next) => { const start = new Date(); await next(); const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); });
      
      







generatorFunction



このアプローチの場合、バージョン2.0からフレームワークの一部ではなくなったcoライブラリを接続する必要があります。

 app.use(co.wrap(function *(ctx, next) { const start = new Date(); yield next(); const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }));
      
      







koa v1のレガシーミドルウェアもサポートされています。 上記の例がアップストリーム/ダウンストリームの場所を理解することを願っています。 (そうでない場合は、コメントを書いてください)



ctxリクエストのコンテキストには、私たちにとって重要な2つのリクエストおよびレスポンスオブジェクトがあります。 ミドルウェアを作成する過程で、指定されたリンクを使用してこれらのオブジェクトの一部のプロパティを分析し、アプリケーションで使用できるプロパティとメソッドの完全なリストを取得できます。



ECMAScriptのドキュメントをすべて引用するまで練習に移ります



最初のミドルウェアを書く



最初の例では、「Hello world」の機能を拡張し、リクエストの処理時間を示す追加のヘッダーを応答に追加します。別のミドルウェアはアプリケーションへのすべてのリクエストをログに書き込みます。 行こう:

 const Koa = require('koa'); const app = new Koa(); // x-response-time app.use(async function (ctx, next) { const start = new Date(); await next(); const ms = new Date() - start; ctx.set('X-Response-Time', `${ms}ms`); }); // logger app.use(async function (ctx, next) { const start = new Date(); await next(); const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(ctx => { ctx.body = 'Hello World'; }); app.listen(3000);
      
      







最初のミドルウェアは現在の日付を保存し、ダウンストリームステージで応答としてヘッダーを書き込みます。

2番目も同じことを行います。ヘッダーに書き込むのではなく、コンソールに表示します。



次のメソッドがミドルウェアで呼び出されない場合、現在のメソッドの後に接続されているすべてのミドルウェアが要求処理に参加しないことに注意してください。



サンプルをテストするときは、バベルを接続することを忘れないでください



エラーハンドラー



このジョブコアはゴージャスに対処します。 たとえば、エラーが発生した場合に、json-format 500でユーザーにエラーを返し、メッセージプロパティにエラーに関する情報を返します。



最初に作成したミドルウェアは次のとおりです。

 app.use(async (ctx, next) => { try { await next(); } catch (err) { // will only respond with JSON ctx.status = err.statusCode || err.status || 500; ctx.body = { message: err.message }; } })
      
      







それだけです。「新しいエラーを投げる」(「私のエラー」)の助けを借りて、ミドルウェアで例外をスローするか、別の方法でエラーを引き起こすことができます。チェーンに沿ってハンドラーに「ポップアップ」し、アプリケーションが正しく応答します。



この知識は、小さなRESTサービスを作成するのに十分なはずだと思います。 もちろん、私以外の人が興味を持たない限り、記事の第2部でこれに対処します。



便利なリンク






All Articles