koa編--koa 2における異常処理の仕組み

14521 ワード

以前はExpressを使ってwebサービスを構築していましたが、3.xから4.xまで、最近KOA 2と接触し始めました.突然春の風に感じるようになりました.Expressよりもっと簡潔な文法以外に、彼の異常処理メカニズムも目の前を明るくします.前にExpressを使った時に異常な捕獲でよく使われた方法はありますか?
  • は、符号ブロックにおいてtry catchによって異常を捕捉し、この方法は非同期処理に対して捕捉できない.
  • は、Trapプロセスにおけるuncaght Exceptionイベントを採用しており、このような処理は、一方でエラーが発生したときに要求に応答できなくなり、サーバメモリがオーバーフローしてしまう可能性がある.
  • はdomain方式を採用していますが、Nodejs公式はこのAPIを放棄しました.具体的な原因は不明です.
  • 以上のように、Expressは異常な捕獲と処理において比較的に煩雑であり、上の3つの方法を組み合わせることで、より強固なコードを作成することができる.しかし、KOAの異常処理は比較的簡単で分かりやすいです.
    異常捕獲
    KOAはどうやって異常を捕まえたのかを見てみます.
    const http = require('http');
    const https = require('https');
    const Koa = require('koa');
    const app = new Koa();
    app.use((ctx)=>{
      str="hello koa2";//      
      ctx.body=str;
    })
    app.on("error",(err,ctx)=>{//          
       console.log(new Date(),":",err);
    });
    http.createServer(app.callback()).listen(3000);
    上のコードを実行してブラウザにアクセスして返した結果は「インターナショナルServer error」です.エラーが発生した時、バックエンドのプログラムは死んでいませんでした.ただ異常を投げました.先端も同時にエラーフィードバックを受けました.expressよりずっと簡単ですか?実はKOAでもExpressでも異常は要求の処理中に発生します.KOAにとっては中間部品の実行中に異常が発生しますので、中間部品の実行中に異常を捕獲して処理すれば大丈夫です.中間部品の実行過程を見てみます.まず中間部品のuseを追加する方法です.
      use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        if (isGeneratorFunction(fn)) {
          deprecate('Support for generators will be removed in v3. ' +
                    'See the documentation for examples of how to convert old middleware ' +
                    'https://github.com/koajs/koa/blob/master/docs/migration.md');
          fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || '-');
        this.middleware.push(fn);
        return this;
      }
    fnは3種類の関数、一般関数、generator関数、そしてasync関数です.最後にGEneratorはasync関数に変換されます.asyncとgeneratorについてはasync関数の意味と使い方を見てもいいです.したがって、最終的な中間部品配列は普通の関数とasync関数だけがあります.
    中間部品の対象となるmiddlewareの使用を見に来ました.
     const fn = compose(this.middleware);
    koa-compose
    return function (context, next) {
        // last called middleware #
        let index = -1
        return dispatch(0)
        function dispatch (i) {
          if (i <= index) return Promise.reject(new Error('next() called multiple times'))
          index = i
          let fn = middleware[i]
          if (i === middleware.length) fn = next
          if (!fn) return Promise.resolve()
          try {
            return Promise.resolve(fn(context, function next () {
              return dispatch(i + 1)
            }))
          } catch (err) {
            return Promise.reject(err)
          }
        }
      }
    }
    
    composeの役割はすべての中間部品を中間部品の自己実行チェーンを生成することです.これは最初の中間部品を実行するだけで、後の中間部品は順次実行します.すべてのミドルウェアがPromiseとしてパッケージされていることが分かりますが、実はここで初めて見た時はやはりモングルがあります.主にasyncとPromise.resoveとnew Promiseの違いが分かりません.Promise.resoveとは、成功した状態に戻るPromiseのオブジェクトです.
    new Promise((resolve)=>resolve());  
    同理Promise.rejectは
    new Promise((resolve,reject)=>reject())
    具体的には、KOA 2の中間部品の自己実行実現について、私は後から専門的に分析します.やはり実行中の異常捕獲と処理を分析します.前に述べたように、middlewareには2種類の関数の普通関数とasync関数しかないので、中間部品ではこの2種類の関数の実行時の異常な捕獲を考慮します.まず普通関数です.普通関数の正常異常は直接try catchに捕獲されて失敗状態のPromiseに戻りますが、普通関数に非同期コードを書くと、非同期コードで発生した異常時に捕獲できなくなります.
    app.get("/",(ctx,next)=>{setTimeout(()=> new Error("this is an error"),1000)})
    KOA 2ではこの非同期プログラムの使用を推奨しないので、非同期プログラムはすべてasyn関数を使用して非同期コードブロックに変換できます.二つ目はasync関数です.
      async function getFile(){
          console.info(`start time[${new Date()}]`);
          try{
             await readFile();
          }catch(e){
             console.log("occur error:",e);
          }
          console.info(`endtime[${new Date()}]`);
      }
      function readFile(){
         return new Promise((resolve,reject)=>{
             setTimeout(()=>{resolve("ok")},1000)
         })
      }
      getFile();
    実はasync関数のawaitは、resoloved状態のPromiseオブジェクトを待っています.簡単に言えば、awaitはthenのシンタックスキャンディーです.catchがないので、異常は捕獲されません.try/catchを使って異常を捕捉し、それに応じた論理処理を行う必要があります.私たちはdispatchでこのコードを見ました.
      try {
            return Promise.resolve(fn(context, function next () {
              return dispatch(i + 1)
            }))
          } catch (err) {
            return Promise.reject(err)
          }
    このコードを通じて中間部品の実行チェーンが順調に実行終了した時に、一つのrejectied状態のPromiseに戻り、異常が発生した時にrejectied状態のPromiseに戻ります.aplication.jsでは下記のコードでこの二つの状態を処理します.
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
    最終的にPromiseオブジェクトのcatchで異常を捕獲します.
    異常処理
    異常捕獲には二つの処理があります.一つはエラー要求に応答して、グローバルエラーイベントの登録をトリガすることです.例えば、エラーログを記録することです.まず、前に述べた異常が発生した時、フロントエンドが受け取った「Internal Server error」はどうやって来たのですか?
    catch後にctx.onerrorに渡して処理します.ctx.onerrorはcontext.jsに定義があります.
    context.js
    onerror(err) {
        //  err    
        if (null == err) return;
    
        if (!(err instanceof Error)) err = new Error(util.format('non-error thrown: %j', err));
    
        let headerSent = false;
        if (this.headerSent || !this.writable) {
          headerSent = err.headerSent = true;
        }
    
        //  Node          error  
        this.app.emit('error', err, this);
    
        if (headerSent) {
          return;
        }
    
        const { res } = this;
    
        // first unset all headers
        if (typeof res.getHeaderNames === 'function') {
          res.getHeaderNames().forEach(name => res.removeHeader(name));
        } else {
          res._headers = {}; // Node < 7.7
        }
    
        // then set those specified
        this.set(err.headers);
    
        //       text/plain
        this.type = 'text';
    
        // ENOENT support
        if ('ENOENT' == err.code) err.status = 404;
    
        // default to 500
        if ('number' != typeof err.status || !statuses[err.status]) err.status = 500;
    
        //       
        const code = statuses[err.status];
        const msg = err.expose ? err.message : code;
        this.status = err.status;
        this.length = Buffer.byteLength(msg);
        this.res.end(msg);
      }
    };
    
    このようにして、異常が発生すると、プログラムは自発的に異常を投げ出します.同時にエラーの要求応答が発生します.私達は手動でエラー情報を返す必要がありません.
    そしてグローバルのカスタム異常処理をトリガします.上のコードにはthis.ap.emitが現れます.ここでグローバルのerrorイベントを触発しました.どこで登録されたかを見てみます.アプリに出てきました.
    callback() {
        const fn = compose(this.middleware);
    
        if (!this.listenerCount('error')) this.on('error', this.onerror);
    
        const handleRequest = (req, res) => {
          const ctx = this.createContext(req, res);
          return this.handleRequest(ctx, fn);
        };
    
        return handleRequest;
      }
    thisはnodeのイベントオブジェクトを継承していますので、onを通じてグローバルイベントを登録できます.サービスは起動時にデフォルトでerrorグローバルイベントを登録します.nodeのイベントメカニズムは同じイベントを複数回登録することができます.メンテナンスはこのイベントのイベント配列ですので、errorイベントを設定することができます.これは一番前に書いたものです.
    app.on("error",(err,ctx)=>{//          
       console.log(new Date(),":",err);
    });
    異常発生時に実行できます.総じてKOAの異常捕獲と処理の核心はtry catchとnodejsのイベントメカニズムである.なんですか?try catch、間違いなく彼です.前に彼は非同期を捕まえられないと言っていますか?彼は非同期異常を捕まえられませんでしたが、KOAではasync awaitを使っています.彼は現在の非同期コードを同期処理に変えました.そうかもしれません.このような理解が正しいかどうか分かりません.訂正を歓迎します.