ข้อผิดพลาด: ไม่สามารถตั้งค่าส่วนหัวหลังจากพวกเขาถูกส่งไปยังลูกค้า


724

ฉันค่อนข้างใหม่กับ Node.js และฉันมีปัญหาบางอย่าง

ฉันใช้ Node.js 4.10 และ Express 2.4.3

เมื่อฉันพยายามที่จะเข้าถึงhttp://127.0.0.1:8888/auth/facebookฉันจะถูกเปลี่ยนเส้นทางไปhttp://127.0.0.1:8888/auth/facebook_callback

ฉันได้รับข้อผิดพลาดดังต่อไปนี้:

Error: Can't render headers after they are sent to the client.
    at ServerResponse.<anonymous> (http.js:573:11)
    at ServerResponse._renderHeaders (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:64:25)
    at ServerResponse.writeHead (http.js:813:20)
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/auth.strategies/facebook.js:28:15
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:113:13
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/strategyExecutor.js:45:39)
    at [object Object].pass (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/authExecutionScope.js:32:3)
    at [object Object].halt (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/authExecutionScope.js:29:8)
    at [object Object].redirect (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/authExecutionScope.js:16:8)
    at [object Object].<anonymous> (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/auth.strategies/facebook.js:77:15)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:195:11)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at param (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:189:13)
    at pass (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:191:10)
    at Object.router [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:197:6)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at Object.auth [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:153:7)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at param (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:189:13)
    at pass (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:191:10)
    at Object.router [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:197:6)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at Object.auth [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:153:7)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at Object.auth [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:153:7)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at HTTPServer.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:211:3)
    at Object.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:105:14)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at HTTPServer.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:211:3)
    at Object.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:105:14)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:323:9
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:338:9

node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:323:9
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:338:9
    at Array.<anonymous> (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session/memory.js:57:7)
    at EventEmitter._tickCallback (node.js:126:26)

ต่อไปนี้เป็นรหัสของฉัน:

var fbId= "XXX";
var fbSecret= "XXXXXX";
var fbCallbackAddress= "http://127.0.0.1:8888/auth/facebook_callback"

var cookieSecret = "node";     // enter a random hash for security

var express= require('express');
var auth = require('connect-auth')
var app = express.createServer();


app.configure(function(){
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    app.use(express.cookieParser());
    app.use(express.session({secret: cookieSecret}));
    app.use(auth([
        auth.Facebook({
            appId : fbId,
            appSecret: fbSecret,
            callback: fbCallbackAddress,
            scope: 'offline_access,email,user_about_me,user_activities,manage_pages,publish_stream',
            failedUri: '/noauth'
        })
    ]));
    app.use(app.router);
});


app.get('/auth/facebook', function(req, res) {
  req.authenticate("facebook", function(error, authenticated) {
    if (authenticated) {
      res.redirect("/great");
      console.log("ok cool.");
      console.log(res['req']['session']);
    }
  });
});

app.get('/noauth', function(req, res) {
  console.log('Authentication Failed');
  res.send('Authentication Failed');
});

app.get('/great', function( req, res) {
  res.send('Supercoolstuff');
});

app.listen(8888);

ฉันขอทราบรหัสของฉันได้ไหม


คำตอบง่ายๆจาก visionmedia: github.com/visionmedia/express/issues/634
shi11i

2
Google ส่งฉันมาที่คำถามนี้ แต่ ExpressJS เวอร์ชันใหม่กว่ามีบูลีนres.headersSentซึ่งสามารถใช้เพื่อตรวจสอบว่าปลอดภัยในการตั้งค่า / ส่งส่วนหัวหรือไม่
Julian Soro

คำตอบ:


1112

resวัตถุใน Express เป็น subclass ของNode.js ของhttp.ServerResponse ( อ่านแหล่ง http.js ) คุณได้รับอนุญาตจะเรียกได้บ่อยเท่าที่คุณต้องการจนกว่าคุณเรียกres.setHeader(name, value) res.writeHead(statusCode)หลังจากนั้นwriteHeadส่วนหัวจะถูกอบและคุณสามารถโทรres.write(data)ได้และในที่สุดres.end(data)เท่านั้น

ข้อผิดพลาด "ข้อผิดพลาด: ไม่สามารถตั้งค่าส่วนหัวหลังจากส่งได้" หมายความว่าคุณอยู่ในสถานะ Body หรือเสร็จสิ้นแล้ว แต่บางฟังก์ชันพยายามตั้งค่าส่วนหัวหรือ statusCode เมื่อคุณเห็นข้อผิดพลาดนี้ให้พยายามค้นหาสิ่งที่พยายามส่งส่วนหัวหลังจากร่างบางส่วนถูกเขียนไปแล้ว ตัวอย่างเช่นค้นหาการโทรกลับที่ถูกเรียกโดยบังเอิญสองครั้งหรือข้อผิดพลาดใด ๆ ที่เกิดขึ้นหลังจากส่งข้อมูล

ในกรณีของคุณคุณเรียกres.redirect()ซึ่งทำให้การตอบสนองกลายเป็นเสร็จสิ้น จากนั้นรหัสของคุณทำให้เกิดข้อผิดพลาด ( res.reqคือnull) และเนื่องจากข้อผิดพลาดเกิดขึ้นภายในจริงของคุณfunction(req, res, next)(ไม่ได้อยู่ในการติดต่อกลับ), การเชื่อมต่อก็สามารถที่จะจับมันแล้วพยายามส่ง 500 หน้าข้อผิดพลาด แต่เนื่องจากส่วนหัวถูกส่งไปแล้ว Node.js ก็setHeaderโยนข้อผิดพลาดที่คุณเห็น

รายการที่ครอบคลุมของวิธีการตอบสนอง Node.js / Express และเมื่อพวกเขาจะต้องเรียกว่า:

การตอบสนองจะต้องอยู่ในหัวและยังคงอยู่ในหัว :

  1. res.writeContinue()
  2. res.statusCode = 404
  3. res.setHeader(name, value)
  4. res.getHeader(name)
  5. res.removeHeader(name)
  6. res.header(key[, val]) (ด่วนเท่านั้น)
  7. res.charset = 'utf-8' (ด่วนเท่านั้นมีผลกับวิธีการเฉพาะด่วนเท่านั้น)
  8. res.contentType(type) (ด่วนเท่านั้น)

การตอบสนองจะต้องอยู่ในหัวและกลายเป็นร่างกาย :

  1. res.writeHead(statusCode, [reasonPhrase], [headers])

การตอบสนองสามารถอยู่ในส่วนหัว / ร่างกายและยังคงอยู่ในร่างกาย :

  1. res.write(chunk, encoding='utf8')

การตอบสนองสามารถอยู่ในส่วนหัว / ร่างกายและกลายเป็นเสร็จสิ้น :

  1. res.end([data], [encoding])

การตอบสนองอาจอยู่ในส่วนหัว / ร่างกายและยังคงอยู่ในสถานะปัจจุบัน:

  1. res.addTrailers(headers)

การตอบสนองจะต้องอยู่ในหัวและกลายเป็นเสร็จสิ้น :

  1. return next([err]) (เชื่อมต่อ / ด่วนเท่านั้น)
  2. ข้อยกเว้นใด ๆ ภายในมิดเดิลแวร์function(req, res, next)(เชื่อมต่อ / ด่วนเท่านั้น)
  3. res.send(body|status[, headers|status[, status]]) (ด่วนเท่านั้น)
  4. res.attachment(filename) (ด่วนเท่านั้น)
  5. res.sendfile(path[, options[, callback]]) (ด่วนเท่านั้น)
  6. res.json(obj[, headers|status[, status]]) (ด่วนเท่านั้น)
  7. res.redirect(url[, status]) (ด่วนเท่านั้น)
  8. res.cookie(name, val[, options]) (ด่วนเท่านั้น)
  9. res.clearCookie(name[, options]) (ด่วนเท่านั้น)
  10. res.render(view[, options[, fn]]) (ด่วนเท่านั้น)
  11. res.partial(view[, options]) (ด่วนเท่านั้น)

13
ใช่ตรวจสอบการโทรถัดไป () หรือ cb อื่น ๆ สองครั้ง
Tony Gutierrez

3
ลิงก์ด่วนดูเหมือนว่าตาย
Korhan Ozturk

25
ระวังข้อผิดพลาดคลาสสิกนี้: res.redirect () ไม่หยุดการประมวลผลคำสั่ง ... ดังนั้นส่งคืนหลังจากนั้น มิฉะนั้นจะเรียกใช้รหัสอื่นซึ่งอาจทำให้เกิดข้อผิดพลาดส่วนหัวที่มีชื่อเสียงโดยไม่ได้ตั้งใจ ขอบคุณสำหรับคำอธิบาย!
KLoozen

เป็นความคิดที่ดีที่จะใช้การส่งคืนเมื่อสิ้นสุดการติดต่อกลับของคุณเพื่อหลีกเลี่ยงปัญหานี้
thethakuri

4
ฉันทำผิดพลาดเล็กน้อยในมิดเดิลแวร์ของฉันฉันไม่ได้ทำreturnมาก่อนnext()ขอบคุณสิ่งนี้ชี้ให้ฉันเห็นข้อผิดพลาด!
illcrx

113

ฉันพบข้อผิดพลาดนี้เช่นกันชั่วครู่หนึ่ง ฉันคิดว่า (หวังว่า) ฉันเอาหัวล้อมรอบฉันต้องการเขียนไว้ที่นี่เพื่อการอ้างอิง

เมื่อคุณเพิ่มมิดเดิลแวร์เพื่อเชื่อมต่อหรือแสดง (ซึ่งสร้างขึ้นบนการเชื่อมต่อ) โดยใช้app.useวิธีการคุณจะต่อท้ายรายการServer.prototype.stackในการเชื่อมต่อ (อย่างน้อยกับกระแสnpm install connectซึ่งดูค่อนข้างแตกต่างจาก GitHub หนึ่งโพสต์นี้) เมื่อเซิร์ฟเวอร์ได้รับการร้องขอมันวนซ้ำสแต็กเรียก(request, response, next)วิธีการ

ปัญหาคือถ้าหนึ่งในรายการมิดเดิลแวร์เขียนไปยังเนื้อหาการตอบสนองหรือส่วนหัว (ดูเหมือนว่ามันเป็นอย่างใดอย่างหนึ่ง / หรือด้วยเหตุผลบางอย่าง) แต่ไม่โทรresponse.end()และคุณโทรnext()แล้วเป็นServer.prototype.handleวิธีการหลักเสร็จสมบูรณ์ก็จะสังเกตเห็น ที่:

  1. ไม่มีรายการเพิ่มเติมในสแต็กและ / หรือ
  2. นั่นresponse.headerSentเป็นเรื่องจริง

ดังนั้นจึงเกิดข้อผิดพลาด แต่ข้อผิดพลาดที่เกิดขึ้นนั้นเป็นเพียงการตอบสนองขั้นพื้นฐาน (จากhttp.jsซอร์สโค้ดเชื่อมต่อ:

res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Cannot ' + req.method + ' ' + req.url);

ที่นั่นก็คือการโทรres.setHeader('Content-Type', 'text/plain');ซึ่งคุณน่าจะได้ตั้งไว้ในrenderวิธีการของคุณโดยไม่ต้องเรียกการตอบกลับ end ()สิ่งที่ชอบ:

response.setHeader("Content-Type", "text/html");
response.write("<p>Hello World</p>");

วิธีที่ทุกอย่างจะต้องมีโครงสร้างเป็นเช่นนี้:

ดีมิดเดิลแวร์

// middleware that does not modify the response body
var doesNotModifyBody = function(request, response, next) {
  request.params = {
    a: "b"
  };
  // calls next because it hasn't modified the header
  next();
};

// middleware that modify the response body
var doesModifyBody = function(request, response, next) {
  response.setHeader("Content-Type", "text/html");
  response.write("<p>Hello World</p>");
  response.end();
  // doesn't call next()
};

app.use(doesNotModifyBody);
app.use(doesModifyBody);

Middleware ที่มีปัญหา

var problemMiddleware = function(request, response, next) {
  response.setHeader("Content-Type", "text/html");
  response.write("<p>Hello World</p>");
  next();
};

มิดเดิลแวร์ที่มีปัญหาจะตั้งค่าส่วนหัวการตอบสนองโดยไม่ต้องโทรresponse.end()และโทรnext()ซึ่งสร้างความสับสนให้กับเซิร์ฟเวอร์ของการเชื่อมต่อ


7
+1 นี่เป็นคำอธิบายที่ดี แต่ถ้าคุณใช้ res.redirect () ต้องทำอย่างไร ฉันพบปัญหานี้บ่อยครั้งเมื่อมิดเดิลแวร์พยายามเปลี่ยนเส้นทางตามเงื่อนไขบางประการ มิดเดิลแวร์ควรไม่เปลี่ยนเส้นทางตามตัวอย่าง "Good Middleware" ของคุณหรือไม่
qodeninja

คุณรู้ว่าฉันมีปัญหาตรงนี้เนื่องจากสิ่งที่คุณเรียกว่ามิดเดิลแวร์ที่มีปัญหา แต่ฉันต้องการกรณีที่ฉันกลับมาตอบสนอง แต่ต้องการที่จะดำเนินการเพิ่มเติมในตัวควบคุมแยกเป็นส่วนหนึ่งของห่วงโซ่ฉันจะไปเกี่ยวกับการปราบปรามข้อผิดพลาดนี้ ?
iQ

57

คำตอบบางส่วนในคำถาม & คำตอบนี้ผิด คำตอบที่ได้รับการยอมรับนั้นไม่ได้ "เชิงปฏิบัติ" มากนักดังนั้นฉันต้องการโพสต์คำตอบที่อธิบายสิ่งต่าง ๆ ในแง่ที่ง่ายขึ้น คำตอบของฉันจะครอบคลุม 99% ของข้อผิดพลาดที่ฉันเห็นโพสต์ซ้ำแล้วซ้ำอีก สำหรับเหตุผลที่แท้จริงที่อยู่เบื้องหลังข้อผิดพลาดให้ดูที่คำตอบที่ยอมรับได้


HTTP ใช้รอบที่ต้องการหนึ่งการตอบสนองต่อคำขอ เมื่อลูกค้าส่งคำขอ (เช่น POST หรือ GET) เซิร์ฟเวอร์ควรส่งการตอบกลับหนึ่งครั้งเท่านั้น

ข้อความแสดงข้อผิดพลาดนี้:

ข้อผิดพลาด: ไม่สามารถตั้งค่าส่วนหัวหลังจากส่งได้

มักเกิดขึ้นเมื่อคุณส่งการตอบกลับหลายครั้งสำหรับคำขอเดียว ตรวจสอบให้แน่ใจว่ามีการเรียกใช้ฟังก์ชันต่อไปนี้เพียงครั้งเดียวต่อคำขอ:

  • res.json()
  • res.send()
  • res.redirect()
  • res.render()

(และอีกสองสามอย่างที่ไม่ค่อยได้ใช้งานให้ตรวจสอบคำตอบที่ยอมรับ)

การเรียกกลับเส้นทางจะไม่ส่งคืนเมื่อเรียกใช้ฟังก์ชัน res เหล่านี้ มันจะทำงานต่อไปจนกว่าจะถึงจุดสิ้นสุดของฟังก์ชั่นหรือคำสั่งกลับ return res.send()หากคุณต้องการที่จะกลับมาเมื่อมีการส่งการตอบสนองคุณสามารถทำมันเพื่อต้องการ:


ยกตัวอย่างโค้ดนี้:

app.post('/api/route1', function(req, res) {
  console.log('this ran');
  res.status(200).json({ message: 'ok' });
  console.log('this ran too');
  res.status(200).json({ message: 'ok' });
}

เมื่อคำขอ POST ถูกส่งไปยัง/ api / route1มันจะเรียกใช้ทุกบรรทัดในการเรียกกลับ ไม่สามารถตั้งหัวหลังจากที่พวกเขาจะถูกส่งข้อความข้อผิดพลาดจะถูกโยนเพราะres.json()จะเรียกว่าเป็นครั้งที่สองหมายถึงการตอบสนองทั้งสองจะถูกส่ง

สามารถส่งคำตอบเดียวต่อการร้องขอ!


ข้อผิดพลาดในตัวอย่างโค้ดด้านบนนั้นชัดเจน ปัญหาทั่วไปคือเมื่อคุณมีหลายสาขา:

app.get('/api/company/:companyId', function(req, res) {
  const { companyId } = req.params;
  Company.findById(companyId).exec((err, company) => {
      if (err) {
        res.status(500).json(err);
      } else if (!company) {
        res.status(404).json();      // This runs.
      }
      res.status(200).json(company); // This runs as well.
    });
}

เส้นทางนี้กับการเรียกกลับที่แนบมาพบ บริษัท ในฐานข้อมูล เมื่อทำแบบสอบถามสำหรับ บริษัท ที่ไม่มีอยู่เราจะเข้าไปในelse ifสาขาและส่งการตอบกลับ 404 หลังจากนั้นเราจะดำเนินการต่อในคำสั่งถัดไปซึ่งจะส่งคำตอบ ตอนนี้เราได้ส่งคำตอบสองรายการแล้วและข้อความแสดงข้อผิดพลาดจะเกิดขึ้น เราสามารถแก้ไขรหัสนี้ได้โดยทำให้แน่ใจว่าเราส่งการตอบกลับเพียงครั้งเดียว:

.exec((err, company) => {
  if (err) {
    res.status(500).json(err);
  } else if (!company) {
    res.status(404).json();         // Only this runs.
  } else {
    res.status(200).json(company);
  }
});

หรือกลับมาเมื่อมีการส่งคำตอบ:

.exec((err, company) => {
  if (err) {
    return res.status(500).json(err);
  } else if (!company) {
    return res.status(404).json();  // Only this runs.
  }
  return res.status(200).json(company);
});

คนบาปใหญ่คือฟังก์ชั่นแบบอะซิงโครนัส รับฟังก์ชั่นจากคำถามนี้เช่น:

article.save(function(err, doc1) {
  if (err) {
    res.send(err);
  } else {
    User.findOneAndUpdate({ _id: req.user._id }, { $push: { article: doc._id } })
    .exec(function(err, doc2) {
      if (err) res.send(err);
      else     res.json(doc2);  // Will be called second.
    })

    res.json(doc1);             // Will be called first.
  }
});

ที่นี่เรามีฟังก์ชั่นแบบอะซิงโครนัส ( findOneAndUpdate()) ในตัวอย่างโค้ด หากไม่มีข้อผิดพลาด ( err) findOneAndUpdate()จะถูกเรียก เนื่องจากฟังก์ชั่นนี้ไม่ตรงกันเวลาres.json(doc1)จะถูกเรียกทันที findOneAndUpdate()สมมติว่ามีข้อผิดพลาดในไม่มี res.json(doc2)ในelseนั้นจะถูกเรียกว่า ขณะนี้มีการส่งคำตอบสองรายการและข้อความแจ้งข้อผิดพลาดส่วนหัวไม่สามารถตั้งค่าได้

res.json(doc1)การแก้ไขในกรณีนี้จะมีการลบ ทั้งในการส่งเอกสารกลับไปยังลูกค้าในอื่นอาจจะเขียนเป็นres.json()res.json({ article: doc1, user: doc2 })


2
คุณอยู่ในฟังก์ชั่นไม่ตรงกันและต้องreturnres.json
Genovo

ปัญหาของฉันถูกใช้res.sendในการวนซ้ำ
Maihan Nijat

1
สิ่งนี้ช่วยฉันในตอนท้ายเพื่อทำความเข้าใจและแก้ไขปัญหาขอบคุณมาก :)
Pankaj Parkar

ขอบคุณมากคุณประหยัดเวลาของฉัน
Mohammad Faisal

นี่เป็นคำตอบที่ดีที่สุดอย่างแน่นอน!
Juanma Menendez

53

ฉันมีปัญหาเดียวกันนี้และตระหนักว่าเป็นเพราะฉันกำลังโทรres.redirectโดยไม่มีreturnคำสั่งดังนั้นnextฟังก์ชั่นก็ถูกเรียกทันทีหลังจากนั้น:

auth.annonymousOnly = function(req, res, next) {
    if (req.user) res.redirect('/');
    next();
};

ซึ่งควรจะได้รับ:

auth.annonymousOnly = function(req, res, next) {
    if (req.user) return res.redirect('/');
    next();
};

43

ผู้คนจำนวนมากประสบข้อผิดพลาดนี้ มันทำให้เกิดความสับสนกับการประมวลผล async เป็นไปได้ว่ารหัสบางส่วนของคุณกำลังตั้งค่าส่วนหัวในช่วงแรกและจากนั้นคุณกำลังเรียกใช้การเรียกกลับแบบ async ในอนาคต ในระหว่างนั้นส่วนหัวการตอบสนองจะถูกส่ง แต่จากนั้นส่วนหัวเพิ่มเติม (เช่นการเปลี่ยนเส้นทาง 30X) พยายามที่จะเพิ่มส่วนหัวพิเศษ แต่มันช้าเกินไปเนื่องจากส่วนหัวการตอบสนองได้ถูกส่งไปแล้ว

ฉันไม่แน่ใจว่าสิ่งที่ทำให้เกิดข้อผิดพลาดของคุณคืออะไร แต่ดูที่การโทรกลับเพื่อตรวจสอบ

เคล็ดลับง่ายๆในการทำให้โค้ดของคุณง่ายขึ้น กำจัดapp.configure()และเพียงโทรapp.useโดยตรงในขอบเขตระดับบนสุดของคุณ

ดูเพิ่มเติมโมดูลeveryauthซึ่งใช้ Facebook และผู้ให้บริการการพิสูจน์ตัวตนบุคคลที่สามอื่น ๆ


การเปลี่ยนเส้นทาง 30X เป็นรหัสตอบกลับ HTTP w3.org/Protocols/rfc2616/rfc2616-sec10.html รหัส 300-399 มีการเปลี่ยนเส้นทางที่แตกต่างกันโดยทั่วไปจะใช้ 302 และ 301 เพื่อส่งไคลเอ็นต์ไปยัง URL สำรอง เมื่อคุณทำ response.redirect (... ) ในโหนดส่วนหัวของการเปลี่ยนเส้นทาง 30X จะถูกส่งไปในการตอบกลับ
Peter Lyons

3
Ohhhh ฉันจินตนาการว่าจะมีการเปลี่ยนเส้นทาง 30 ครั้งติดต่อกันหรือบางสิ่งบางอย่าง
Janac Meena

17

ฉันต้มหัวของฉันในเรื่องนี้และมันเกิดขึ้นเนื่องจากความผิดพลาดในการจัดการโทรกลับ การโทรกลับที่ไม่ส่งคืนทำให้การตอบกลับถูกตั้งค่าสองครั้ง!

โปรแกรมของฉันมีรหัสที่ตรวจสอบคำขอและสอบถามฐานข้อมูล หลังจากตรวจสอบว่ามีข้อผิดพลาดเกิดขึ้นฉันโทรกลับ index.js ด้วยข้อผิดพลาดในการตรวจสอบ และถ้าการตรวจสอบผ่านมันไปข้างหน้าและกด db ด้วยความสำเร็จ / ล้มเหลว

    var error = validateRequestDetails("create",queryReq);
    if (error)
        callback(error, null);
   else
    some code 
    callback(null, success);

สิ่งที่เกิดขึ้นคือ: การตรวจสอบกรณีล้มเหลวการโทรกลับได้รับการเรียกและการตอบสนองได้รับการตั้งค่า แต่ไม่กลับมา ดังนั้นมันยังคงดำเนินต่อไปวิธีการไปที่ฐานข้อมูลและตีสำเร็จ / ความล้มเหลว มันเรียกการติดต่อกลับเดิมอีกครั้งทำให้การตอบสนองถูกตั้งค่าสองครั้งในขณะนี้

ดังนั้นการแก้ปัญหาจึงเป็นเรื่องง่ายคุณต้อง 'คืนค่า' การเรียกกลับเพื่อให้วิธีการไม่ดำเนินการต่อเมื่อเกิดข้อผิดพลาดและตั้งค่าวัตถุตอบสนองทันที

  var error = validateRequestDetails("create",queryReq);
    if (error)
        callback(error, null);
        return;
    else
       some code 
       callback(null, success);

1
ขอบคุณ! นี่กลายเป็นปัญหาของฉันเช่นกัน เพิ่งทำ ctrl + f และพบ a ที่callback(...)ไม่มี a return;หลังจากนั้นซึ่งในที่สุดทำให้res.send(...)ถูกเรียกสองครั้ง

15

ข้อผิดพลาดประเภทนี้คุณจะได้รับเมื่อคุณส่งคำสั่งหลังจากส่งคำตอบ

ตัวอย่างเช่น:

res.send("something response");
console.log("jhgfjhgsdhgfsdf");
console.log("sdgsdfhdgfdhgsdf");
res.send("sopmething response");

จะส่งผลให้เกิดข้อผิดพลาดที่คุณเห็นเพราะเมื่อมีการส่งคำตอบแล้วres.sendจะไม่มีการดำเนินการต่อไปนี้

หากคุณต้องการทำอะไรคุณควรทำก่อนส่งคำตอบ


นี่เป็นปัญหาของฉันแน่นอน :)
โจเอล Balmer

6

บางครั้งคุณอาจได้รับข้อผิดพลาดนี้เมื่อคุณพยายามเรียกใช้ฟังก์ชัน next () หลังจากres.endหรือres.sendลองลบถ้าคุณมีถัดไป () หลังจาก res.send หรือ res.end ในฟังก์ชั่นของคุณ หมายเหตุ: ที่นี่ถัดไป () หมายถึงหลังจากตอบสนองลูกค้าด้วยการตอบสนองของคุณ ( เช่น res.send หรือ res.end ) คุณยังคงพยายามรันโค้ดบางส่วนเพื่อตอบสนองอีกครั้งดังนั้นจึงไม่ถูกกฎหมาย

ตัวอย่าง:

router.get('/',function (req,res,next){
     res.send("request received");
     next(); // this will give you the above exception 
});

ลบออกnext()จากฟังก์ชั่นด้านบนและมันจะทำงาน


6

หากคุณใช้ฟังก์ชั่นการโทรกลับใช้returnหลังจากerrบล็อก นี่เป็นหนึ่งในสถานการณ์ที่ข้อผิดพลาดนี้สามารถเกิดขึ้นได้

userModel.createUser(data, function(err, data) {
    if(err) {
      res.status = 422
      res.json(err)
      return // without this return the error can happen.
    }
    return res.json(data)
  })

ผ่านการทดสอบกับ Node version v10.16.0และ express4.16.4


4

ข้อผิดพลาดนี้เกิดขึ้นเมื่อคุณส่งคำตอบ 2 ครั้ง ตัวอย่างเช่น :

if(condition A)
{ 

      res.render('Profile', {client:client_});

}

if (condition B){

      res.render('Profile', {client:client_});
    }
  }

ลองจินตนาการดูว่าเงื่อนไข A และ B นั้นเป็นจริงในวินาทีนั้นrenderคุณจะได้รับข้อผิดพลาดนั้น


3

ในกรณีของฉันมันเป็น 304 การตอบสนอง (แคช) ที่ทำให้เกิดปัญหา

วิธีที่ง่ายที่สุด:

app.disable('etag');

โซลูชันสำรองที่นี่หากคุณต้องการการควบคุมเพิ่มเติม:

http://vlasenko.org/2011/10/12/expressconnect-static-set-last-modified-to-now-to-avoid-304-not-modified/


ในกรณีของฉันยัง 304 การตอบสนอง ฉันกำลังใช้ไฟเบอร์สำหรับการประมวลผล ไม่ว่าคำตอบของคุณจะช่วยอะไรมากมาย ขอบคุณ
Dileep stanley

ใครสามารถอธิบายสิ่งที่เกี่ยวข้องกับการลบส่วนหัว etag?
mattwilsn

2
ETags อนุญาตให้เซิร์ฟเวอร์ไม่ส่งเนื้อหาที่ไม่เปลี่ยนแปลง การปิดใช้งานจะปิดคุณสมบัตินี้ รายการ ETag wikipedia ( en.wikipedia.org/wiki/HTTP_ETag ) มีคำอธิบายที่ยาวกว่า
blended

3

ในกรณีของฉันสิ่งนี้เกิดขึ้นกับ React และ zip.js เมื่อฉันไม่ได้ยกเลิกการสมัครจากช่องในการcomponentWillUnmountติดต่อกลับขององค์ประกอบ React ของฉัน


2

สำหรับทุกคนที่มาที่นี่และไม่มีวิธีแก้ไขปัญหาอื่นใดที่ช่วยเหลือในกรณีของฉันสิ่งนี้ปรากฏบนเส้นทางที่จัดการกับการอัปโหลดภาพ แต่ไม่ได้จัดการกับการหมดเวลาและดังนั้นหากการอัปโหลดใช้เวลานานเกินไปและหมดเวลาหลังจากส่งการตอบกลับหมดเวลาแล้วการเรียก res.send () ส่งผลให้เกิดความผิดพลาดเนื่องจากส่วนหัวถูกตั้งค่าเป็นบัญชีสำหรับการหมดเวลาแล้ว

นี่คือการทำซ้ำได้อย่างง่ายดายโดยการตั้งค่าการหมดเวลาที่สั้นมากและกดปุ่มเส้นทางด้วยภาพที่มีขนาดใหญ่ decently ความผิดพลาดได้รับการทำซ้ำทุกครั้ง


1
คุณจัดการกับการหมดเวลาเพื่อหลีกเลี่ยงปัญหานี้อย่างไร

2

เพียงแค่เอนตัวนี้ คุณสามารถส่งคำตอบผ่านฟังก์ชั่นนี้:

app.use(function(req,res,next){
  var _send = res.send;
  var sent = false;
  res.send = function(data){
    if(sent) return;
    _send.bind(res)(data);
    sent = true;
};
  next();
});

2

เพิ่ม middlware นี้และมันจะทำงาน

app.use(function(req,res,next){
 var _send = res.send;
var sent = false;
res.send = function(data){
    if(sent) return;
    _send.bind(res)(data);
    sent = true;
};
next();
});

2

สิ่งนี้เกิดขึ้นเมื่อมีการส่งคำตอบไปยังลูกค้าและคุณพยายามให้คำตอบอีกครั้ง คุณต้องตรวจสอบในรหัสของคุณว่าบางแห่งที่คุณกำลังตอบกลับลูกค้าอีกครั้งซึ่งทำให้เกิดข้อผิดพลาดนี้ ตรวจสอบและส่งคืนการตอบกลับหนึ่งครั้งเมื่อคุณต้องการกลับมา


1

ฉันมีปัญหานี้เมื่อฉันทำรังตามสัญญา สัญญาที่อยู่ในสัญญาจะคืนค่า 200 ไปยังเซิร์ฟเวอร์ แต่จากนั้นคำสั่ง catch ของสัญญานอกจะส่งคืน 500 เมื่อฉันแก้ไขสิ่งนี้ปัญหาจะหายไป


คุณแก้ไขปัญหานี้ได้อย่างไร ฉันมีปัญหาเดียวกันกับสัญญา ฉันไม่สามารถหลีกเลี่ยงการวางซ้อน ... ดังนั้นฉันจะหยุดการดำเนินการที่คำสั่ง return ได้อย่างไร
saurabh

1

มาที่นี่จากnuxtปัญหาอยู่ในasyncDataวิธีการของส่วนประกอบฉันลืมที่จะreturnสัญญาซึ่งดึงข้อมูลและการตั้งค่าส่วนหัวที่นั่น


1

โปรดตรวจสอบว่ารหัสของคุณส่งคืนคำสั่ง res.send () หลายรายการสำหรับคำขอเดียวหรือไม่ เช่นเมื่อฉันมีปัญหานี้ ....

ฉันเป็นปัญหานี้ในแอปพลิเคชันโหนดที่ฉันเรียกคืน ความผิดพลาดก็คือ

switch (status) { case -1: res.send(400); case 0: res.send(200); default: res.send(500); }

ฉันจัดการกรณีต่าง ๆ โดยใช้สวิตช์โดยไม่หยุดพัก สำหรับผู้ที่มีความคุ้นเคยเล็กน้อยเกี่ยวกับตัวเรือนสวิตช์ให้รู้ว่าไม่หยุดพักให้ส่งคืนคำหลัก โค้ดภายใต้ case และบรรทัดถัดไปของมันจะถูกดำเนินการไม่ว่าจะเกิดอะไรขึ้น ดังนั้นแม้ว่าฉันต้องการที่จะส่ง res.s เดียวเนื่องจากข้อผิดพลาดนี้มันกลับมาหลายคำสั่ง res.send ซึ่งได้รับแจ้ง

ข้อผิดพลาดไม่สามารถตั้งค่าส่วนหัวหลังจากที่พวกเขาถูกส่งไปยังลูกค้า ซึ่งได้รับการแก้ไขโดยการเพิ่มหรือใช้ผลตอบแทนก่อนแต่ละ res.send () วิธีการเช่นผลตอบแทน res.send (200)

switch (status) { case -1: res.send(400); break; case 0: res.send(200); break; default: res.send(500); break; }


ขอบคุณสำหรับแรงบันดาลใจของคุณที่เกิดขึ้นกับฉัน ฉันแก้ไขมันด้วยเงื่อนไขอื่นด้วย
Amr AbdelRahman

1

อาจเป็นไปได้ว่านี่เป็นสิ่งโหนดมากกว่า 99% ของเวลาที่มีการติดต่อกลับสองครั้งที่ทำให้คุณตอบกลับสองครั้งหรือครั้งต่อไป มันแก้ไขปัญหาของฉันได้โดยใช้ next () ภายในลูป ลบถัดไป () ออกจากลูปหรือหยุดเรียกมันมากกว่าหนึ่งครั้ง


1

ฉันพบข้อผิดพลาดคล้ายกันเมื่อฉันพยายามส่งการตอบกลับภายในฟังก์ชันลูป ทางออกที่ง่ายคือการย้าย

res.send ('ส่งคำตอบ');

ออกจากลูปเนื่องจากคุณสามารถส่งส่วนหัวการตอบกลับได้เพียงครั้งเดียว

https://www.tutorialspoint.com/nodejs/nodejs_response_object.htm



1

ฉันมีปัญหาเดียวกันซึ่งเกิดจากพังพอน

เพื่อแก้ไขปัญหาที่คุณต้องเปิดการใช้งานPromisesเพื่อให้คุณสามารถเพิ่ม: รหัสของคุณซึ่งจะช่วยให้การใช้mongoose.Promise = global.Promisenative js promises

ทางเลือกอื่น ๆ ในการแก้ปัญหานี้คือ:

var mongoose = require('mongoose');
// set Promise provider to bluebird
mongoose.Promise = require('bluebird');

และ

// q
mongoose.Promise = require('q').Promise;

แต่คุณต้องติดตั้งแพ็คเกจเหล่านี้ก่อน


1

ข้อผิดพลาดค้นหาด้วยตัวเองหลังจาก RND:

1) รหัสข้อผิดพลาดของฉัน:

return res.sendStatus(200).json({ data: result });

2) รหัสสำเร็จของฉัน

return res.status(200).json({ data: result });

ความแตกต่างก็คือว่าผมใช้sendStatus ()แทนสถานะ ()


0

ใน typescript ปัญหาของฉันคือฉันไม่ได้ปิดการเชื่อมต่อ websocket หลังจากได้รับข้อความ

WebSocket.on("message", (data) => {
    receivedMessage = true;
    doSomething(data);
    localSocket.close(); //This close the connection, allowing 
});

0

หากคุณไม่ได้รับความช่วยเหลือจากด้านบน: สำหรับ noobs เหตุผลที่อยู่เบื้องหลังข้อผิดพลาดนี้คือการส่งคำขอหลายครั้งให้เราเข้าใจจากบางกรณี: - 1 '

module.exports = (req,res,next)=>{
        try{
           const decoded  = jwt.verify(req.body.token,"secret");
           req.userData = decoded;
           next();
        }catch(error){
            return res.status(401).json({message:'Auth failed'});
        }
        next();   
        }

`ในการโทรข้างต้นถัดไป () สองครั้งจะทำให้เกิดข้อผิดพลาด

  1. router.delete('/:orderId', (req, res, next) => { Order.remove({_id:req.params.orderId},(err,data)=>{ if(err){ **res.status(500).json(err);** }else{ res.status(200).json(data); } *res.status(200).json(data);* }) })

ที่นี่การตอบสนองคือการส่งสองครั้งตรวจสอบว่าคุณได้ส่งคำตอบแล้ว


0

ในกรณีของฉันมันเกิดขึ้นเนื่องจากการโทรกลับหลายครั้ง ฉันได้เรียกnext()วิธีการหลายครั้งในระหว่างรหัส


0

ปัญหาของฉันคือฉันมีการsetIntervalทำงานซึ่งมีif/elseบล็อกซึ่งเป็นclearIntervalวิธีการภายในelse:

      const dataExistsInterval = setInterval(async () => {
        const dataExists = Object.keys(req.body).length !== 0;
        if (dataExists) {
          if (!req.files.length) {
            return res.json({ msg: false });
          } else {
              clearInterval(dataExistsInterval);
            try {
            . . .

วางclearIntervalก่อนที่if/elseจะหลอกลวง



-1

ทั้งหมดที่ฉันต้องทำในกรณีที่เกิดข้อผิดพลาดนี้คือ res.end ()

 auth.annonymousOnly = function(req, res, next) {
 // add other task here   
   res.end();    
  };

ปัญหาอื่น ๆ ที่คุณอาจประสบคือมีรหัสหลังจาก res.json และ res เขียน. ในกรณีนี้คุณต้องใช้ Return เพื่อหยุดการทำงานหลังจากนั้น

 auth.annonymousOnly = function(req, res, next) {

  if(!req.body.name)
  {
    res.json({error:"some error"});
    return;
  }
  let comp = "value"; // this is the code after res.json which may cause some problems so you have to use return 
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.