Node.JSでの集中例外処理





翻訳者からの序文:数か月前、node.jsで記述されたゲームサーバーで例外を使用する機能のソリューションを探していました。 残念ながら、純粋な形式の例外は、イベントループで実行されている環境と完全に互換性があるわけではありません。 これを説明する最も簡単な方法は、例を使用することです。

try { process.nextTick(function() { throw new Error('Catch Me If You Can'); }); } catch (e) { console.log('Exception caught:', e); }
      
      





もちろん、この例外はキャッチされず、プロセス全体がドロップされます。 1か月前、node.jsバージョン0.8.0は、そのような問題を解決するために設計された新しい(実験的な) ドメインモジュールとともにリリースされました。 しかし、私はまだ使用しているクラスに敬意を表したいと思います。 行こう:



node.jsの関数型プログラミングは、楽しく、表現力豊かで、コンパクトです。 1つの点を除いて-例外処理。 これについてはあまり語られていませんが、私の意見では、エラーと例外を調和的に処理する方法がないことは、node.jsの最大の欠点の1つです。 Node-fibersは完全に命令型のプログラミングスタイルを使用してこれを実現していますが、機能スタイル内で問題を解決したいと思います。



エラーコード(node.jsカーネルの基になっている)の問題は、最初にエラーに遭遇したコードが、ほとんどの場合、エラーへの対応方法を決定するのに間違った場所であることです。 この場合、マルチスレッドシステムのtry / catch構造がより理解しやすくなります。 通常、スタックの上の誰かがエラーの処理方法を知っています。



ただし、ノードなどの非同期システムの問題は、コールバックまたはEventEmitterのいずれかが呼び出されるたびに、イベントループの最上部で呼び出されるか、割り当てたコードとは異なるコードによって呼び出されることです。このリスナー(リスナーを割り当てる場所は、おそらく、それを引き起こした任意のコードよりも潜在的なエラーを処理するのに適した場所です)。 このような状況で例外をスローすると、プログラムがクラッシュする可能性が高くなります。 正しいコードがランタイムエラーをスローする可能性があるJavaScriptには多くの可能性があることを考慮すると、この問題は、ポインターに注意してゼロで除算しない場合に問題を回避できるCよりもさらに悪化します。 はい、単体テストは役立ちますが、ボウルが本当に必要なときに、ふるいのすべての穴を塞ぐ試みです。



これを実現するには、独自のエラーハンドラーでブロック(ブロック)を設定する機能が必要です。これを簡単かつ迅速に作成し、コールバックと共に外部コードに渡すことができます。 次に、コールバックで例外が発生した場合、コールバックを作成したときにアクティブだったブロックエラーハンドラーに例外を送信する必要があります。 これらのソリューションのほとんどは、フューチャーズ、プロミス、ファイバーなどからのものであることがわかりました。 ブロックを作成するためのこのシンプルな機能とともに。 次のスニペットは、必要なことを正確に実行するBlockクラスを説明しています。

 /** *  Block        . */ function Block(errback) { this._parent=Block.current; this._errback=errback; } Block.current=null; /** *  ,   ,     , *           guard(). *     ,   . * * Example: stream.on('end', Block.guard(function() { ... })); */ Block.guard=function(f) { if (this.current) return this.current.guard(f); else return f; }; /** *      .  -     (" try"). *  -    ('catch'). */ Block.begin=function(block, rescue) { var ec=new Block(rescue); return ec.trap(block); }; /** *   function(err),        , *       (  ,    ). *      err  true. * * Example: request.on('error', Block.errorHandler()) */ Block.errorHandler=function() { // Capture the now current Block for later var current=this.current; return function(err) { if (!err) return; if (current) return current.raise(err); else throw err; }; }; /** *    .     error handler,     . *   raise(...)     .    , *        throw. *  ,   error handler'  , *     error handler'. */ Block.prototype.raise=function(err) { if (this._errback) { try { this._errback(err); } catch (nestedE) { if (this._parent) this._parent.raise(nestedE); else throw nestedE; } } else { if (this._parent) this._parent.raise(err); else throw(err); } }; /** *   callback    . *       raise()  . *  return value   undefined   . */ Block.prototype.trap=function(callback) { var origCurrent=Block.current; Block.current=this; try { var ret=callback(); Block.current=origCurrent; return ret; } catch (e) { Block.current=origCurrent; this.raise(e); } }; /** *  ,        . *     trap(),       . */ Block.prototype.guard=function(f) { if (f.__guarded__) return f; var self=this; var wrapped=function() { var origCurrent=Block.current; Block.current=self; try { var ret=f.apply(this, arguments); Block.current=origCurrent; return ret; } catch (e) { Block.current=origCurrent; self.raise(e); } }; wrapped.__guarded__=true; return wrapped; };
      
      







(ブロック/レスキューの用語を選んだのは、Rubyが好きだからではなく、そのようなソリューションではJSで予約されている単語を使用していないためです)。



ご注意 翻訳者:記事の最初からの例ですが、ブロックを使用して、次の形式を取ります。

 Block.begin(function() { process.nextTick(Block.guard(function() { throw new Error; })); }, function(err) { console.log('Exception caught:', err); });
      
      



これで例外が処理され、サーバーを削除できなくなりました。 これは、setTimeout、EventEmitter、データベースクエリのコールバック、その他すべてで機能します。



次に、集中化されたエラー処理のためにブロックを使用する例を見てみましょう。 この例では、 connectミドルウェアが使用されます。この場合、次の関数は優れたエラーハンドラーです。httpクライアントに正しいエラーを返します。 何らかの方法でエラーを自分で処理する必要がある場合、コールバックをfunction(err) { ; next(err); }



function(err) { ; next(err); }



function(err) { ; next(err); }



また、Block.begin呼び出しでインライン関数を使用して、より視覚的に類似させてtry / catchを実行することもできますが、読みやすいように名前付きコールバックを使用することを好みます。



 function handleUserAgent(req, res, next) { return Block.begin(process, next); function process() { jsonifyRequest(req, withRequest); //  ,   jsonifyRequest , //    Block.guard() } function withRequest(requestObj) { var r=validators.UserAgentRecord(requestObj, {fix:true}); if (!r.valid) { res.writeHead(400); return res.end('Invalid request object: ' + r.reason); } var uar=r.object; if (uar.token) { // Verify //return handler.verifyUserAgent(uar); throw new Error('verifyUserAgent not yet implemented'); } else { // Create uar.token=null; uar.type='auth'; // TODO: Maybe support unauth in the future? handler.createUserAgent(uar, Block.guard(withUserAgent)); } } function withUserAgent(userAgent) { var r=validators.UserAgentRecord(userAgent, {fix:true}); return respondJson(r.object, res); } }
      
      







留意すべき主な点は、このコードまたはprocess()関数によってスローされた例外は、エラーハンドラー(この場合は次の関数)にリダイレクトされることです。 ブロックにコールバックを添付するには、Block.guard(originalFunction)を使用してコールバックをラップする必要があります。 これにより、Block.guard()の呼び出し時にアクティブなブロックを記憶し、originalFunction()関数自体を呼び出す前にコンテキストとして復元できます。



コールバックでブロックを明示的に使用する別の例を考えてみましょう。 この場合、HTTPリクエストを作成し、応答テキストを蓄積し、コールバックを呼び出して、作成されたCouchResponseをその中に渡します(これにより、応答と例外をスローする可能性のあるものが解析されます)。



 request: function(options, callback) { var req=http.request(options, function(res) { var text=''; res.setEncoding('utf8'); res.on('data', function(chunk) { text+=chunk; }); res.on('end', Block.guard(function() { callback(new CouchResponse(res, text)); })); res.on('error', Block.errorHandler()); }); req.on('error', Block.errorHandler()); req.end(); }
      
      







プロセス全体をドロップする予期しない例外が発生する可能性のある場所がまだいくつかあります。



Block.guardでラップすることもできますが、これは不要だと思います。 さらに、この場合のエラーは重大であり、ユニットテストでカバーする必要があることを100%確信しています。 ただし、「end」ハンドラーはすぐには表示されない処理を行うため(JSON.parse呼び出しが含まれていることがわかります)、ガードで保護することを好みます。 最後に、標準のerrorHandler()ブロックを使用して、要求および応答エラーイベントをキャッチします。 このシンプルな一元化されたエラー処理パターンにより、これらのエラーの発生場所が明確になり、意味のある任意のレベルでエラーが処理されます。 Block.begin()のネストされた呼び出しを使用できます( try{try{}catch{}}catch{}



類似)。 これはフレームワークコードで役立ちます。フレームワークコードは、他の人のコードによって作成されたブロック内で何らかの作業を行う必要があります。



PS:著者にはFutureの実装とそれらを使用した例があります。 Futureに関連するすべてを翻訳したわけではなく、例を古典的なコールバックの使用に適合させました。



元のテキスト全体を読むことをお勧めします。 node.jsで防弾コードを記述するための10もの推奨事項がリストされています。



githubリポジトリおよびnpmモジュール (npm install control-block)として設計したBlockクラス。



All Articles