質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.63%

expressで、res.json()すると、ヘッダーをセットできない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 797

masaking

score 30

expressにて、reactに情報を渡すためのapi作りにチャレンジしています。

postmanで確認すると、json自体は帰っているのですが、

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
とエラーが出ています。

なぜこのようなエラーが出ていて、どのようにすればエラーが出ずにアプリがアプリがクラッシュしないのでしょうか。


big5db.js

var express = require('express');
var router = express.Router();

const client = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017';

client.connect(url, { useUnifiedTopology: true }, function (err, client) {

    var db = client.db("test_db");
    var collection = db.collection("Big5");

    var query = {};
    var projection = {
        "personality.name": "$personality.name",
        "personality.percentile": "$personality.percentile",
        "personality.trait_id": "$personality.trait_id",
        "_id": 0
    };

    var cursor = collection.find(query).project(projection);


router.get('/',function(req,res,next){
        cursor.forEach(
            function(doc) {
            var data = doc.personality.map(({name, trait_id, percentile}) => ({name, trait_id, percentile}))
                //res.render('big5db.ejs',{big5s: data});
                res.status(200).json(data);
            }, 
            function(err) {
                client.close();
            }
        );
});


router.get('/big5_openness',(req,res,next)=>{
        cursor.forEach(
            function(doc) {
                var data = doc.personality[0].children.map(({name,percentile}) => ({name,percentile}))
                return res.json(data);
            }
        );
    });

router.get('/big5_conscientiousness',(req,res,next)=>{
        cursor.forEach(
            function(doc) {
                var data = doc.personality[1].children.map(({name,percentile}) => ({name,percentile}))
                return res.json(data);
            }
        );
    });

router.get('/big5_extraversion',(req,res,next)=>{
        cursor.forEach(
            function(doc) {
                var data = doc.personality[2].children.map(({name,percentile}) => ({name,percentile}))
                return res.json(data);
            }
        );
    });

router.get('/big5_agreeableness',(req,res,next)=>{
        cursor.forEach(
            function(doc) {
                var data = doc.personality[3].children.map(({name,percentile}) => ({name,percentile}))
                return res.json(data);
            }
        );
    });

router.get('/big5_neuroticism',(req,res,next)=>{
        cursor.forEach(
            function(doc) {
                var data = doc.personality[4].children.map(({name,percentile}) => ({name,percentile}))
                return res.json(data);
            }
        );
    });

});

module.exports = router;

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var big5db = require('./routes/big5db');
var twitter = require('./routes/twitter');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/twitter', twitter);
app.use('/big5db', big5db);


// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

例えば、http://localhost:5001/big5db/にゲットリクエストを送ると、以上のようなエラーが出ます。
参考などを見たモノの、一つのパスにつき、resは一回だけにしているので、なぜこれでheaderを二回もセットしているよ、とエラーが出るのかわかりません。

基本的なこととは思いますが、ご教授いただけると幸いです。
また、express-generatorで雛形を構築しております。
それを使わないバージョンでも試したのですが、やはり同じ状況でした。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

一つのパスにつき、resは一回だけにしている

残念ながら、cursor.forEach ということは複数の結果に対してそれぞれ callback が呼ばれるので、一回ではなく複数回になると思います。(結果が空なら 0 回の可能性もあるのでは。)

mongodb は詳しくありませんが、ドキュメントには toArray というメソッドがありますので、これを使ったら良いのではないでしょうか。
参考: toArray - Class: Cursor

また、cursor は一回しか作られないのに、それをリクエストのたびに使ってるので、二回目以降の API アクセスは動かないのでは…。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/02 08:53

    ありがとうございます。
    Arrayで出力した後、繰り返しで抜き出す処理をすることで対応できました。
    ありがとうございます。

    キャンセル

checkベストアンサー

0

なぜこのようなエラーが出ていて、

db の走査(forEach()のループ)を完了する前に、HTTP応答しているためです。

どのようにすればエラーが出ずにアプリがアプリがクラッシュしないのでしょうか

走査の完了を検知する仕組みを実装し、応答処理を1回にします。

追記)
「router で切り分け > dbコネクト > 走査」という実装ではないので、エラーでclient.close() されると、再接続されるのかな?という点も気になります。(追記ここまで


データの一覧は配列をJSONのstringデータで応答」という目標で、以下のような方法を思いつきます。

ご質問の big5db.js より

  /* omitted */

  // 非同期の場合、カウント上限を取得しておく。
  let foundItems = cursor.count();

  router.get('/',function(req,res,next){
    let datas = []; // 応答用の配列を準備

    // データベース走査する cursor.forEach() 
    cursor.forEach(
      function(doc) {

        datas.push( Object.create(doc.personality) );

        // 非同期の場合、data.length でカウント
        if( datas.length === foundItems ) {
          res.status(200).json(datas);
        }

      }, 
      function(err) {
        client.close();
      }
    );
    // 同期であればこう?
    //res.status(200).json(datas);

  }); // router.get('/')

  /* omitted */

Cursor Methods - MongoDB を参照しましたが、最新のコード実装までは追えていませんので、
上記コードは「答案」に過ぎませんが、解決の糸口になればと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/02 08:52

    ありがとうございます。
    何をループ処理の中に入れて、何を外に書くのか、コネクトからクローズの一連の流れは中で、レスポンスはその外に書く、ということを学びました。

    ありがとうございます!!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る