嘿!你有沒有發現,在撰寫程式的時候,總是會有一些程式碼不斷地重複出現?它們就像是影分身術一樣,在不同的地方出現,做著類似的事情。這些重複的程式碼不僅讓我們的程式變得冗長,也讓修改和維護變得更加困難。
但是,身為一個聰明的開發者,我們怎麼能讓這些重複的程式碼繼續存在呢?就像孫悟空掌握了七十二變一樣,我們也要學會如何把這些重複的程式碼"變"出去,讓我們的程式碼變得更加簡潔和優雅。
除了重複的程式碼,錯誤處理也是我們經常要面對的問題。就像在玩遊戲時遇到 boss 一樣,錯誤總是在關鍵時刻出現,讓我們的程式陷入困境。但是,如果我們提前準備好對策,就可以輕鬆地應對這些錯誤,讓我們的程式穩定運行。
所以,讓我們一起來看看如何通過抽取重複的程式碼和實現全面的錯誤處理,讓我們的程式變得更加強大和可靠吧!相信學會這些技巧,你也能成為一名出色的開發者!
首先,我們在專案的根目錄下新增一個名為 utils
的資料夾,用於存放我們抽取出來的工具函式。
在 utils
資料夾中,我們新增一個名為 handleSuccess.js
的檔案,並添加以下程式碼:
// utils/handleSuccess.js
function handleSuccess(res, data, message, statusCode) {
res.status(statusCode).json({
statusCode: statusCode || 200,
status: "success",
message,
data,
});
}
module.exports = handleSuccess;
這個 handleSuccess
函式用於處理成功回應。它接收四個參數:
res
: Express 的回應物件data
: 要在回應中返回的資料message
: 成功訊息statusCode
: HTTP 狀態碼,預設為 200res.status()
方法設置 HTTP 狀態碼,然後使用 res.json()
方法返回一個包含狀態碼、狀態、訊息和資料的 JSON 物件。接下來,我們在 utils
資料夾中新增一個名為 appError.js
的檔案,並添加以下程式碼:
// utils/appError.js
const appError = (httpStatus, errMessage, next) => {
const error = new Error(errMessage);
error.message = errMessage;
error.statusCode = httpStatus;
error.isOperational = true;
return error;
};
module.exports = appError;
這個 appError
函式用於創建自定義的錯誤物件。它接收三個參數:
httpStatus
:HTTP 狀態碼errMessage
:錯誤訊息next
:Express 的 next
函式Error
物件,並設置錯誤的屬性,包括錯誤訊息、HTTP 狀態碼和一個表示錯誤是否為操作錯誤的標誌 isOperational
。最後,函式返回這個錯誤物件。app.js
中進行處理。utils
資料夾中新增一個名為 handleErrorAsync.js
的檔案,並添加以下程式碼:// utils/handleErrorAsync.js
const handleErrorAsync = function handleErrorAsync(func) {
return function (req, res, next) {
func(req, res, next).catch(function (error) {
return next(error);
});
};
};
module.exports = handleErrorAsync;
這個 handleErrorAsync
函式是一個高階函式,用於處理異步函式的錯誤。它接收一個異步函式 func
作為參數,並返回一個新的函式。
這個新的函式會執行原始的異步函式 func
,並使用 catch
方法捕獲可能發生的錯誤。如果發生錯誤,就將錯誤傳遞給 Express 的 next
函式,交給錯誤處理中間件進行處理。
使用 handleErrorAsync
函式可以簡化異步函式的錯誤處理,避免了繁瑣的 try...catch
語句。
有了上面的工具函式,我們就可以在 Express 應用程式中實現更全面的錯誤處理了。
我們回到 app.js
檔案:
添加一個全域的未捕獲異常處理程式:
// 程式出現重大錯誤時
process.on("uncaughtException", (err) => {
// 記錄錯誤下來,等到服務都處理完後,停掉該 process
console.error("Uncaughted Exception!");
console.error(err);
process.exit(1);
});
這個處理程式會在程式出現未捕獲的異常時觸發。它會記錄錯誤訊息,並在服務處理完所有請求後,停止該 process。
接下來,我們添加一個 404 錯誤處理中間件:
// 404 錯誤
app.use(function (req, res, next) {
res.status(404).json({
status: "error",
message: "無此路由資訊",
path: req.originalUrl,
});
});
這個中間件會在沒有匹配到任何路由時觸發,返回一個 404 錯誤訊息。
然後,我們添加一個開發環境的錯誤處理函式:
const resError = (err, res) => {
res.status(err.statusCode).json({
message: err.message,
statusCode: err.statusCode,
isOperational: err.isOperational,
stack: err.stack,
});
};
這個函式用於處理開發環境下的錯誤,返回詳細的錯誤訊息,包括錯誤堆疊。
接下來,我們添加一個全域的錯誤處理中間件:
app.use(function (err, req, res, next) {
err.statusCode = err.statusCode || 500;
if (err.name === "ValidationError") {
err.message = "資料欄位未填寫正確,請重新輸入!";
err.isOperational = true;
return resError(err, res);
}
resError(err, res);
});
這個中間件會捕獲所有的錯誤,並進行處理。如果是資料驗證錯誤,就設置相應的錯誤訊息和 isOperational
標誌,然後呼叫 resError
函式處理錯誤。如果是其他類型的錯誤,就直接呼叫 resError
函式處理。
最後,我們添加一個未捕獲的 Promise 錯誤處理程式:
// 未捕獲到的 catch
process.on("unhandledRejection", (err, promise) => {
console.error("未捕獲到的 rejection:", promise, "原因:", err);
});
module.exports = app;
這個處理程式會在有未捕獲的 Promise 錯誤時觸發,記錄錯誤訊息。
現在,我們可以在 Express 的路由中使用之前定義的工具函式了。
我們回到 routes/feedback.js
檔案,並在檔案的頂部引入工具函式:
const handleSuccess = require("../utils/handleSuccess");
const handleErrorAsync = require("../utils/handleErrorAsync");
const appError = require("../utils/appError");
然後,在新增 feedback 的路由中,我們可以使用 handleErrorAsync
函式來處理異步函式的錯誤:
router.post(
"/",
handleErrorAsync(async (req, res, next) => {
const { contactPerson, phone, email, feedback } = req.body;
// 必填欄位驗證
if (!contactPerson || !email || !feedback) {
// 使用 appError 函式創建錯誤物件
// 狀態碼為 400,錯誤訊息為 "姓名、信箱、內容為必填欄位"
return next(appError(400, "姓名、信箱、內容為必填欄位"));
}
const newFeedback = await Feedback.create({
contactPerson,
phone,
email,
feedback,
});
// 使用 handleSuccess 函式處理成功回應
// 狀態碼為 201,回應中包含新創建的 feedback 資料和成功訊息
handleSuccess(res, newFeedback, "新增成功", 201);
})
);
讓我們詳細解釋一下新增的錯誤和成功處理部分:
appError
函式創建一個自定義的錯誤物件。appError
函式接收兩個參數:HTTP 狀態碼和錯誤訊息。在這裡,我們將狀態碼設為 400(Bad Request),表示客戶端提交的請求有問題。錯誤訊息設為 "姓名、信箱、內容為必填欄位",明確指出哪些欄位是必填的。return next(appError(...))
,將錯誤物件傳遞給 Express 的 next
函式。這會將錯誤交給 Express 的錯誤處理中間件進行處理。handleSuccess
函式來處理成功回應。handleSuccess
函式接收四個參數:
res
:Express 的回應物件,用於發送回應給客戶端。newFeedback
:新創建的 feedback 資料,作為回應的一部分返回給客戶端。handleSuccess
函式內部,它會設置適當的 HTTP 狀態碼(如果沒有提供,預設為 200),並將包含狀態、訊息和資料的 JSON 回應返回給客戶端。通過使用 appError
和 handleSuccess
函式,我們將錯誤處理和成功回應的邏輯抽象到了單獨的函式中。這樣做的好處是:
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
var cors = require("cors");
const indexRouter = require("./routes/index");
const usersRouter = require("./routes/users");
const feedbackRoute = require("./routes/feedback");
const mongoose = require("mongoose");
const dotenv = require("dotenv");
dotenv.config({ path: "./config.env" });
const Feedback = require("./models/feedback");
var app = express();
// 程式出現重大錯誤時
process.on("uncaughtException", (err) => {
// 記錄錯誤下來,等到服務都處理完後,停掉該 process
console.error("Uncaughted Exception!");
console.error(err);
process.exit(1);
});
const DB = process.env.DATABASE.replace(
"<password>",
process.env.DATABASE_PASSWORD
);
mongoose
.connect(DB)
.then(() => console.log("資料庫連接成功"))
.catch((err) => {
console.log("MongoDB 連接失敗:", err);
});
app.use(cors());
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("/feedbacks", feedbackRoute);
// 404 錯誤
app.use(function (req, res, next) {
// 回應一個包含錯誤訊息的 JSON 對象
res.status(404).json({
status: "error",
message: "無此路由資訊",
path: req.originalUrl, // 提供更多的上下文信息
});
});
// * 開發環境 錯誤處理
const resError = (err, res) => {
res.status(err.statusCode).json({
message: err.message,
statusCode: err.statusCode,
isOperational: err.isOperational,
stack: err.stack,
});
};
app.use(function (err, req, res, next) {
err.statusCode = err.statusCode || 500;
if (err.name === "ValidationError") {
err.message = "資料欄位未填寫正確,請重新輸入!";
err.isOperational = true;
return resError(err, res);
}
resError(err, res);
});
// 未捕捉到的 catch
process.on("unhandledRejection", (err, promise) => {
console.error("未捕捉到的 rejection:", promise, "原因:", err);
});
// 導出給 ./bin/www 使用
module.exports = app;
在本文中,我們學習了如何將重複的程式碼抽取到單獨的函式或模組中,提高程式碼的可讀性和可維護性。我們創建了 handleSuccess
、appError
和 handleErrorAsync
三個工具函式,分別用於處理成功回應、創建自定義錯誤物件和處理異步函式的錯誤。
然後,我們在 Express 應用程式中實現了更全面的錯誤處理,包括全域的未捕獲異常處理、404 錯誤處理、開發環境錯誤處理、全域錯誤處理中間件以及未捕獲的 Promise 錯誤處理。
最後,我們在 Express 的路由中使用了這些工具函式,簡化了路由的錯誤處理邏輯,提高了程式碼的可讀性。
希望通過本文的學習,你能夠更好地理解如何抽取重複的程式碼,並實現更全面的錯誤處理。如果你有任何問題或建議,歡迎在評論區留言討論。
(對了,如果你覺得今天的內容對你有幫助,別忘了給個讚支持一下喔!這會是我繼續努力的動力呢~)