编辑:谢谢大家的帮助。我最终使用异步等待完全重写了代码,以提高可读性。该问题不再存在。
当我尝试在/ cart上查看网站的电子商务购物车时,出现以下错误:
Example app listening on port 8080
/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongodb/lib/utils.js:132
throw err;
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at validateHeader (_http_outgoing.js:500:11)
at ServerResponse.setHeader (_http_outgoing.js:507:3)
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/controllers/cart.js:99:9
at Function.<anonymous> (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:3928:16)
at parallel (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:2078:12)
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/parallel.js:35:9
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/once.js:12:16
at iteratorCallback (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/eachOf.js:52:13)
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/onlyOnce.js:12:16
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/node_modules/async/internal/parallel.js:32:13
at apply (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/lodash/_apply.js:15:25)
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/lodash/_overRest.js:32:12
at callbackWrapper (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:2047:11)
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:3928:16
at model.$__save.error (/mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/mongoose/lib/model.js:343:7)
at /mnt/c/Users/Gernene/Documents/Programming/journal-supplies-co/node_modules/kareem/index.js:297:21
此错误似乎是由以下行引起的:
Cart.create({token: token, discount: null}, function(err, cart) {
if (err || !cart) throw err;
res.setHeader('Set-Cookie', cookie.serialize("cart_token", token, {
path: "/",
maxAge: 60 * 60 * 24 * 7 // 1 week
}));
在研究了一些类似的问题之后,我相当确定上面的几行与这些冲突:
res.render("cart/index", {
cartItems: cartItems,
products: products,
cartCount: cartCount,
discount: discount
});
但是,我不确定如何解决此问题,并且可能是错误的。
这是我的完整购物车代码:
module.exports = function(app){
// Dependencies and imported functions
const async = require('async');
const ObjectId = require('mongoose').Types.ObjectId;
const validate = require('../modules/validate');
const paypal = require('paypal-rest-sdk');
const cookie = require('cookie');
const path = require('path');
const appDir = path.dirname(require.main.filename);
const cartMod = require("../modules/cart");
const cartCount = cartMod.itemCount;
const crypto = require("crypto");
// DB
const Cart = require('../models/carts');
const CartItem = require('../models/cart_items');
const Discount = require('../models/discounts');
const Product = require('../models/products');
paypal.configure({
"host" : "api.sandbox.paypal.com",
"port" : "",
'mode': 'sandbox', //sandbox or live
'client_id': process.env.JSC_PAYPAL_CLIENT_ID,
'client_secret': process.env.JSC_PAYPAL_CLIENT_SECRET
});
const payPalPayment = (items, total) => {
return {
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": appDir + "/success",
"cancel_url": appDir + "/cancel"
},
"transactions": [{
"item_list": { "items": items },
"amount": {
"currency": "USD",
"total": total
},
"description": "This is the payment description."
}]
};
};
const createPayPalPayment = (payment, req, res) => {
paypal.payment.create(payment, function(err, payment) {
if (err) {
throw err;
} else {
if(payment.payer.payment_method === 'paypal') {
req.paymentId = payment.id;
var redirectUrl;
for(var i = 0; i < payment.links.length; i++) {
var link = payment.links[i];
if (link.method === 'REDIRECT') {
redirectUrl = link.href;
}
}
res.redirect(redirectUrl);
}
}
});
};
const displayCartItems = (res, cartId, cartCount, discount) => {
CartItem.find({cart: cartId}, function(err, cartItems) {
if (err) return next(err);
var products = [];
async.eachSeries(cartItems,
function(cartItem, next) {
Product.findById(cartItem.product, function(err, product) {
if (err) throw err;
products.push(product);
return next();
});
},
function(err) {
if (err) res.status(400).send("Could not display cart items");
res.render("cart/index", {
cartItems: cartItems,
products: products,
cartCount: cartCount,
discount: discount
});
}
);
});
};
const createCart = (req, res, next) => {
var token = crypto.randomBytes(20).toString("hex");
Cart.create({token: token, discount: null}, function(err, cart) {
if (err || !cart) throw err;
res.setHeader('Set-Cookie', cookie.serialize("cart_token", token, {
path: "/",
maxAge: 60 * 60 * 24 * 7 // 1 week
}));
return next();
});
};
const checkCart = (req, res, next) => {
var token = req.cookies["cart_token"];
if (!token) {
createCart(res, res, next);
} else {
Cart.find({token: token}, function(err, cart) {
if (err || !cart) createCart(res, res, next);
return next();
});
}
};
const addCartItem = (cartId, productId, quantity) => {
CartItem.findOne(
{'product': productId, "cart": cartId},
function(err, item) {
if (err || !item) {
CartItem.create({
cart: cartId,
product: productId,
quantity: quantity
});
} else {
var newQuantity = item.quantity + parseInt(quantity);
CartItem.update(
{'_id': item._id},
{quantity: newQuantity},
function(err, item) {});
}
}
);
};
const updateCartItem = (res, id, quantity) => {
CartItem.findById(id, function(err, item) {
if (err || !item) throw err;
CartItem.update({_id: id}, {quantity: quantity}, function(err, item) {
res.send("Successfully updated cart item quantity.");
});
});
};
const deleteCartItem = (res, id) => {
CartItem.findById(id, function(err, item) {
if (err || !item) throw err;
CartItem.remove({_id: id}, function(err, item) {
res.send("Successfully deleted cart item.");
});
});
};
const cartIndex = (req, res, cartCount) => {
var token = req.cookies["cart_token"];
Cart.findOne({token: token}, function(err, cart) {
if (err || !cart) throw err;
if (cart.discount) {
Discount.findById(cart.discount, function(err, discount) {
if (err || !discount) throw err;
displayCartItems(res, cart._id, discount.percent, cartCount);
});
} else {
displayCartItems(res, cart._id, 0, cartCount);
}
});
};
app.get("/discount", function (req, res) {
cartCount(req, res, function(req, res, cartCount) {
res.render("discount/index", {cartCount: cartCount});
});
});
app.post("/discount", checkCart, function (req, res) {
var code = req.body["code"];
Discount.findOne({code: code}, function(err, discount) {
if (err || !discount) {
cartCount(req, res, function(req, res, cartCount) {
res.render("discount/index", {err: true, cartCount: cartCount});
});
} else {
var cartToken = req.cookies["cart_token"];
Cart.findOne({token: cartToken}, function(err, cart) {
if (err || !cart) throw err;
Cart.update(
{_id: cart._id},
{discount: discount._id},
function(err, cart) {
cartCount(req, res, function(req, res, cartCount) {
res.render("discount/index", {err: false, cartCount: cartCount});
});
}
);
});
}
});
});
app.get('/cart', checkCart, function (req, res, next) {
cartCount(req, res, cartIndex);
});
app.post('/cart', checkCart, function (req, res, next) {
var token = req.cookies["cart_token"];
Cart.findOne({token: token}, function(err, cart) {
if (err || !cart) return next(err);
CartItem.find({cart: cart._id}, function(err, cartItems) {
if (err) return next(err);
var purchases = [];
var total = 0;
async.eachSeries(cartItems,
function(cartItem, next) {
Product.findById(cartItem.product, function(err, product) {
if (err) {
res.status(400).send("Could not find products");
} else {
var purchase = {};
purchase["name"] = product.name;
purchase["sku"] = product.name;
purchase["price"] = product.price;
purchase["currency"] = "USD";
purchase["quantity"] = cartItem.quantity;
purchases.push(purchase);
total += product.price * cartItem.quantity;
}
return next();
});
},
function(err) {
if (err) {
res.status(400).send("Could not display cart items");
} else {
if (cart.discount) {
Discount.findById(cart.discount, function(err, discount) {
if (err || !discount) throw err;
var discountItem = {};
discountItem["name"] = "Discount";
discountItem["sku"] = "Discount";
discountItem["price"] = -1 * (total * discount.percent / 100);
discountItem["currency"] = "USD";
discountItem["quantity"] = 1;
purchases.push(discountItem);
total += discountItem.price;
var payment = payPalPayment(purchases, total);
createPayPalPayment(payment, req, res);
});
} else {
var payment = payPalPayment(purchases, total);
createPayPalPayment(payment, req, res);
}
}
}
);
});
});
});
app.post('/cart/confirm', checkCart, function (req, res, next) {
var cartToken = req.cookies["cart_token"];
Cart.find({token: cartToken}, function(err, cart) {
if (err || !cart) return next(err);
else {
CartItem.find({cart: cart._id}, function(err, cartItems) {
if (err) return next(err);
var products = [];
async.eachSeries(cartItems,
function(cartItem, next) {
Product.findById(cartItem.product, function(err, product) {
if (err) {
res.status(400).send("Could not find products");
} else {
products.push(product);
}
return next();
});
},
function(err) {
if (err) {
res.status(400).send("Could not display cart items");
} else {
executePayPalPayment(req);
res.render("cart/index", {
cartItems: cartItems,
products: products
});
}
}
);
});
}
});
});
app.post('/cart/add/:id', checkCart, function (req, res, next) {
var cartToken = req.cookies["cart_token"];
var productId = req.params.id;
var quantity = validate.sanitize(req.body[`quantity`]);
if (validate.isInt(quantity)) {
Product.findById(productId, function(err, product) {
if (err || !product) throw err;
Cart.findOne({token: cartToken}, function(err, cart) {
if (err || !cart) throw err;
addCartItem(cart._id, product._id, quantity);
res.send("Product added!")
});
});
}
});
app.post('/cart/update/:id', checkCart, function (req, res, next) {
var id = req.params.id;
var quantity = req.body["quantity"];
if (!validate.isInt(quantity)) throw err;
else if (quantity < 1) {
deleteCartItem(res, id);
} else {
updateCartItem(res, id, quantity);
}
});
app.post('/cart/delete/:id', checkCart, function (req, res, next) {
var id = req.params.id;
deleteCartItem(res, id);
});
}
const createCart = (req, res, next) => {
var token = crypto.randomBytes(20).toString("hex");
Cart.create({token: token, discount: null}, function(err, cart) {
if (err || !cart) throw err;
res.setHeader('Set-Cookie', cookie.serialize("cart_token", token, {
path: "/",
maxAge: 60 * 60 * 24 * 7 // 1 week
}));
return next();
});
};
const cartIndex = (req, res, cartCount) => {
var token = req.cookies["cart_token"];
Cart.findOne({token: token}, function(err, cart) {
if (err || !cart) throw err;
if (cart.discount) {
Discount.findById(cart.discount, function(err, discount) {
if (err || !discount) throw err;
displayCartItems(res, cart._id, discount.percent, cartCount);
});
} else {
displayCartItems(res, cart._id, 0, cartCount);
}
});
};
app.get('/cart', checkCart, function (req, res, next) {
cartCount(req, res, cartIndex);
});
}
本质上,我试图检查用户是否有任何cookie可以识别他们的购物车,如果没有,则创建一个新的cookie /购物车。另外,我调用cartCount在每个页面的顶部显示购物车项目的数量。
我非常感谢您的帮助!请让我知道是否需要更多信息!
编辑:
完整的app.js:
// Dependencies
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const mongoose = require("mongoose");
const path = require("path");
const pug = require("pug");
const session = require("express-session");
const validate = require("./modules/validate");
const cartMod = require("./modules/cart");
const cartCount = cartMod.itemCount;
const port = process.env.PORT || 8080;
// DB
const mongoURI = 'mongodb://localhost/db';
mongoose.connect(process.env.MONGODB_URI || mongoURI);
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function() {
// Connected!
});
app.set('trust proxy', 1)
app.set("views", __dirname + "/views");
app.set("view engine", "pug");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(__dirname + "/assets"));
app.use(session({
secret: process.env.JSC_SESSIONS_SECRET,
resave: true,
saveUninitialized: false
}));
// Routes
require("./controllers/cart")(app);
require("./controllers/categories")(app);
require("./controllers/discount")(app);
require("./controllers/nav_pages")(app);
require("./controllers/pages")(app);
require("./controllers/products")(app);
require("./controllers/users")(app);
app.use(function(req, res, next) {
res.status(404);
// respond with html page
if (req.accepts('html')) {
cartCount(req, res, function(req, res, cartCount) {
res.render("errors/404", { cartCount: cartCount, url: req.url });
return;
});
}
});
app.listen(port, function() {
console.log("Example app listening on port " + port)
});
答案 0 :(得分:0)
此错误通常意味着您调用了res.send()
或res.end()
或任何将响应发送到前端的方法。这些方法只能被调用一次,并且在调用它们之后,您将不再与该响应进行交互(例如,如果您之前未调用res.setHeader
,则无法调用res.render
。>
此代码:
async.eachSeries(cartItems,
function(cartItem, next) {
Product.findById(cartItem.product, function(err, product) {
if (err) throw err;
products.push(product);
return next();
});
},
function(err) {
if (err) res.status(400).send("Could not display cart items");
res.render("cart/index", {
cartItems: cartItems,
products: products,
cartCount: cartCount,
discount: discount
});
}
);
如果出现错误(并且触发了res.render
),则您不应再将信息发送给客户端(因为您已经这样做了)。似乎其中一项购物车商品失败,但是您仍然继续与res
对象进行互动。
要解决此问题,请避免在将数据发送到客户端后调用res
。
答案 1 :(得分:0)
如果return
中不存在err
或购物车,则添加checkCart
:
if (err || !cart) return createCart(res, res, next);
return next();