web-dev-qa-db-ja.com

Node.js、Async、Formidableでのエラー処理

次のスニペットでは、最初のasyncメソッドのフィールドを検証したいと思います。

それらが有効でない場合は、すぐにユーザーにエラーを返したいと思います。

それ、どうやったら出来るの?

var form = new formidable.IncomingForm();

async1.series([
    function (callback) {
        form.parse(req);

        form.on('field', function (name, val) {
            // Get the fields
        });

        form.on('fileBegin', function (name, file) {
            if (file.name !== "") {
                file.path = __dirname + '/upload/' + file.name;
            }
        });
        callback();
    },
    function (callback) {
        form.on('file', function (name, file) {
            try {
                // Do something with the file using the fields retrieved from first async method
            }
            catch (err) {
                logger.info(err);
            }
        });


        callback();
    }
], function (err) {
    //the upload failed, there is nothing we can do, send a 500

    if (err === "uploadFailed") {
        return res.send(500);
    }

    if (err) {
        throw err;
    }
    return res.status(200);

});
14

フォームチェックを関数に抽出します。

var form = new formidable.IncomingForm();

function check(name, cb, err) {
 return new Promise((res,rej) => {
  form.on('field', function(n, val) {
        if(n !== name) return;
        if(cb(val)){
          res(val);
        }else{
          rej(err);
       }
   });
 });
}

form.parse(req);

これで、チェックを実装し、Promise.allを使用してそれらを要約できます。

 Promise.all(
   check("username", val => val.length > 4, "username isnt valid"),
   check("password", val => true, "we need a password")
 ).then(_ => res.json({status:200}))
  .catch(err => res.json({err}));

すべてのパラメーターが渡されていない場合、これは際限なく待機します。それで、それが終了した場合は終了しましょう:

const ended = new Promise((_,rej) => form.on("end", () => rej("params required"));

Promise.race(
 ended,
  Promise.all(
   check("username", val => val.length > 4, "username isnt valid"),
   check("password", val => true, "we need a password")
  )
).then(_ => res.json({status:200}))
 .catch(err => res.json({err}));

これにより、適切なデータフローを作成できます。例えば。:

const login = Promise.all(
  //usable as one liners
 check("username", val => val.length >= 8, "username invalid"),
 //or more extensible
 check("password", val => {
   if( val.length < 8 ) return false;
   //other checks
   console.log(password);
   return true;
 }, "password invalid")
//the field values are resolved by the promises so we can summarize them below 
).then(([username,password]) =>
   //a random (maybe async) call to evaluate the credentials
  checkAgainstDB(username,password)
  //we can directly fail here, err is  "password invalid" or "username invalid"
).catch(err => res.json({error:"login failed",details:err}));

 //another parameter can be extra handled    
const data = check("something", val => val.length);

//we need to summarize all the possible paths (login /data in this case) to one that generates the result
Promise.race(
 //here we join them together
 Promise.all(login, data)
   .then((l, d) => res.json(whatever),
 //and we use the ended promise ( from above ) to end the whole thing
 ended
  //and at last the errors that can occur if the response ended or that have not been canceled early
).catch(e => res.json(e));
7
Jonas Wilms
var form = new formidable.IncomingForm();

async1.series([
    function (callback) {
        form.parse(req);

        form.on('field', function (name, val) {
            if (!name || !val) {
              // the moment callback is called with an error, async will stop execution of any of the steps
              // in the series and execute the function provided as the last argument
              // idimoatic node, when calling the callback with instance of Error
              return callback(new Error('InvalidParams'));
            }

            /**
             * This is from async documentation: https://caolan.github.io/async/docs.html#series
             * Run the functions in the tasks collection in series, each one running once the previous function 
             * has completed. If any functions in the series pass an error to its callback, no more functions are 
             * run, and callback is immediately called with the value of the error. Otherwise, callback receives 
             * an array of results when tasks have completed.
             */
        });

        form.on('fileBegin', function (name, file) {
            if (file.name !== "") {
                file.path = __dirname + '/upload/' + file.name;
            }
        });

        form.on('end', function () {
          // call callback with null to specify there's no error
          // if there are some results, call it like callback(null, results);
          return callback(null);
        });

        // if you call the callback immediately after registering event handlers for on('field') etc,
        // there will be no time for those events to be triggered, by that time, this function will be 
        // done executing.
        //callback();
    },
    function (callback) {
        form.on('file', function (name, file) {
            try {
                // Do something with the file using the fields retrieved from first async method
            }
            catch (err) {
                logger.info(err);
                return callback(err);
            }
        });

        // This should also not be called immediately
        //callback();
    }
], function (err) {
    //the upload failed, there is nothing we can do, send a 500

    if (err === "uploadFailed") {
        return res.send(500);
    }

    if (err.message === 'InvalidParams') {
      // This will be immediately returned to the user.
      return res.sendStatus(400);
    }

    if (err) {
      // I'm not sure if this was just for the example, but if not, you should not be throwing an error
      // at run time. 
        throw err;
    }
    return res.status(200);

});

エラーを作成する場所と方法、およびエラーがすぐにユーザーにバブルアップする方法を示す必要があるコードにコメントを追加しました。

参照: 非同期ドキュメント

P.S.コードスニペットは実行可能ではありませんが、コードの表現が優れています。

-編集-

コメントから詳細を知った後、別のスニペットを追加します。コールバックとイベント処理が不当に混在しています。 form.parseにコールバックを渡すだけで、すべてのフィールドが収集されたときにコールバックが呼び出されます。検証を行ったり、エラーをすぐに返したり、フォームフィールドをすぐに処理したりできます。

form.parse(req, function(err, fields, files) {
  if (err) return res.sendStatus(400);
  if (fields.areNotValid()) return res.sendStatus(400);
  // parse fields
});

または、イベントハンドラーを登録することもできます。流入するすべてのイベントは、async.seriesのように同時に処理されます。

var form = new formidable.IncomingForm();

form.parse(req);
form.on('field', (name, val) => {
  if (!name || val) {
    console.log('InvalidParams')
    return res.sendStatus(400);
  }
});
form.on('fileBegin', (name, file) => {
  if (file.name !== "") {
    file.path = __dirname + '/upload/' + file.name;
  }
});
form.on('file', (name, file) => {

});
form.on('error', (err) => {
  console.log('ParsingError');
  return res.sendStatus(400);
})
form.on('end', () => {
  if (res.headersSent) {
    console.log('Response sent already')
  } else {
    // handle what you want to handle at the end of the form when all task in series are finished
    return res.sendStatus(200);
  }
});
3
Neeraj Sharma

これはフィールドが入ってくるときなので、これは検証するのに適した場所だと思います。

form.on('field', function (name, val) {
    //if values are null
    if (!name || !val) {
        //pass the callback an error 
        return callback("Values are null")
    }
    // Get the fields
});

これが役に立ったら教えてください。

1
Dream_Cap