Skip to main content

Error handling and debugging best practice

Overview

Code là kiểu gì cũng có bug. Muốn không có bug thì tốt nhất là không code nữa. Còn code là còn bug. - 1 nhà hiền triết đã nói

Thường thì bất kỳ chương trình nào cũng có thể gây ra các lỗi thời gian chạy. Điều này khá dễ hiểu. Nhưng điểm quan trọng ở đây là cách bạn xử lý lỗi một cách chính xác để không gây ra các tác động phụ khác có thể làm tình hình trở nên tồi tệ hơn. Bây giờ, chúng ta sẽ liệt kê một số thực hành tốt phổ biến nhất trong việc xử lý lỗi trong ứng dụng Avada.

Try/catch

Đó là một trong những điều đầu tiên mà mọi lập trình viên học từ đầu, nhưng không phải tất cả chúng ta biết cách sử dụng nó một cách đúng và hiệu quả nhất, hoặc chúng ta đơn giản quên bọc các block code của mình trong try/catch. Trong một số khung làm việc Node JS như KoaJS, nếu chúng ta không bọc try/catch cho mã của mình, chúng ta có thể gặp vấn đề về thời gian chạy, gây tiêu tốn tài nguyên máy chủ của chúng ta.

Rất recommend rằng các controller, handlers của chúng ta nên có một khối try/catch bao bên ngoài để ngăn chặn việc vượt quá thời gian chạy và xử lý đúng khi có lỗi xuất hiện.

/**
* Controller needs to have try catch at the outermost block to prevent timeout
*
* @returns {Promise<void>}
*/
export default async function someController(ctx) {
try {
// throw new Error("This is an error")

return ctx.status = {
success: true
}
} catch (e) {
console.error(e); // logging the e object for more information
ctx.status = 500;
return ctx.status = {
success: false,
error: e.message
}
}
}

Trong ví dụ ở trên, bạn có thể thấy rằng bạn nên ghi log đối tượng lỗi để bạn có thể xem chi tiết hơn, không chỉ là e.message mà không có thông tin gợi ý để gỡ lỗi. Với đối tượng lỗi, bạn có thông tin về dòng mã chính xác nơi gây ra lỗi.

alt text

Optional chaining (?.)

Use cases

Vấn đề phổ biến khi sử dụng Firestore hoặc cơ sở dữ liệu NOSQL nói chung là không có sự hạn chế nào về schema của cơ sở dữ liệu, dẫn đến sự không nhất quán trong dữ liệu kết quả truy vấn.

Chúng ta đã thấy vấn đề này xuất hiện trên console của chúng tôi hơn một ngàn lần:

Cannot read property 'length' of undefined

ES6 đã có một cú pháp mới để khắc phục vấn đề này, đó là toán tử tùy chọn (optional operator). Trước khi có cú pháp này, bạn có thể quen thuộc với điều này:

const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};

if (adventurer.cat && adventurer.cat.name) {
console.log(adventurer.cat.name); // To prevent issues when accessing property of undefined
}

Optional chaining mới cung cấp kết quả tương tự nhưng với ít công sức hơn. Bạn có thể xem ví dụ dưới đây:

/**
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
* @type {{cat: {name: string}, name: string}}
*/
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};

const dogName = adventurer?.dog?.name;
console.log(dogName);
// expected output: undefined

/**
* @note This syntax can be used with calling a method too
*/
console.log(adventurer.someNonExistentMethod?.());
// expected output: undefined

Reference

In order to see an official documentation about this feature, please visit: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

Try/catch with Promise.all

Trước tiên, cùng nhìn đoạn code sau:

fs = require('fs');

const delay = ms => new Promise(res => setTimeout(res, ms));

const promise1 = async () => {
await delay(1000);
console.log("Promise 1 executed")
throw new Error("This is an error")
}
const promise2 = async () => {
await delay(3000)
fs.writeFileSync('promise.txt', 'This promise works!');
console.log("Promise 2 executed")
};

// Consider this to be a controller
(async () => {
try {
const start = new Date();
await promise1();
await promise2()
const end = new Date() - start
console.info('Execution time: %dms', end)
} catch (e){
console.log("Got an error here")
}
})();

Bạn có để ý rằng function promise1 throw ra một Error không? Câu hỏi ở đây là: Bạn có nghĩ rằng function promise2 sẽ được chạy? Để cùng chạy thử nhé:

alt text

Như bạn có thể thấy, chỉ có promise1 đã được thực thi. Nhưng bạn muốn tất cả đều được thực thi, bất kể kết quả là gì. Bạn có thể gặp tình huống sau:

fs = require('fs');

const delay = ms => new Promise(res => setTimeout(res, ms));

const promise1 = async () => {
await delay(1000);
console.log("Promise 1 executed")
throw new Error("This is an error")
}
const promise2 = async () => {
await delay(3000)
fs.writeFileSync('promise.txt', 'This promise works!');
console.log("Promise 2 executed")
};

// Consider this to be a controller
(async () => {
try {
const start = new Date();
await Promise.all([
promise1(),
promise2()
]); // Promise all the function
const end = new Date() - start
console.info('Execution time: %dms', end)
} catch (e){
console.log("Got an error here")
}
})();

Và kết quả là

alt text

Như bạn có thể thấy, cả hai promise đã được thực thi, nhưng một lỗi đã được ném ra và chúng ta có block catch, có thể kết thúc với response 500 từ bộ điều khiển của chúng ta. Để ngăn chặn điều này, chúng ta có thể có một khối try/catch nhỏ và không thực hiện gì với lỗi. Tuy nhiên, bạn có thể thực hiện các bước sau:

fs = require('fs');

const delay = ms => new Promise(res => setTimeout(res, ms));

const promise1 = async () => {
await delay(1000);
console.log("Promise 1 executed")
throw new Error("This is an error")
}
const promise2 = async () => {
await delay(3000)
fs.writeFileSync('promise.txt', 'This promise works!');
console.log("Promise 2 executed")
};

// Consider this to be a controller
(async () => {
try {
const start = new Date();
await Promise.all([
promise1(),
promise2()
].map(p => p.catch(e => e)));
const end = new Date() - start
console.info('Execution time: %dms', end)
} catch (e){
console.log("Got an error here")
}
})();

Now both got executed and no errors thrown out

alt text

Error Debugging and tracing logs on GCP

Frontend Debugging with Resource Override