Lỗi gặp phải
TypeError: Cannot read properties of undefined (reading 'getValue')
Bạn đã gọi .getValue() — hoặc .getValues(), .getRange(), hay hàm tương tự — trên một biến có giá trị undefined tại thời điểm chạy. Ba nguyên nhân phổ biến nhất: lặp qua các hàng với chỉ số sai, tham chiếu đến một sheet không tồn tại, hoặc giả định rằng getActiveSheet() luôn trả về giá trị hợp lệ.
Nguyên nhân gốc rễ
Apps Script chạy trên V8 JavaScript. Gọi một phương thức trên undefined là bạn sẽ nhận ngay lỗi này — không có ngoại lệ. Đây là những nguyên nhân thường gặp nhất:
- Chỉ số mảng vượt giới hạn: Bạn lặp với
rows[i]nhưng mảng có ít phần tử hơn bạn nghĩ —rows[10]trên mảng 10 hàng sẽ trả vềundefined. - Tên sheet sai:
spreadsheet.getSheetByName('Data')trả vềnullkhi sheet không tồn tại. Gọi.getRange()trên giá trịnullđó sẽ gây lỗi tương tự. - Xử lý sai vùng dữ liệu rỗng:
getValues()trên vùng rỗng trả về mảng 2 chiều gồm các chuỗi rỗng, không phảiundefined. Nhưng nếu đọc nhầm kích thước, bạn vẫn có thể truy cập vào ôundefined. - Lệch một đơn vị khi đánh chỉ số hàng: Các phương thức của Sheet như
getRange(row, col)dùng chỉ số bắt đầu từ 1. Mảng từgetValues()dùng chỉ số bắt đầu từ 0. Nhầm lẫn hai hệ thống này và bạn sẽ đọc vượt ra ngoài cuối mảng.
Cách sửa 1: Kiểm tra tham chiếu sheet trước khi dùng
Lấy sheet theo tên? Đừng giả định nó tồn tại. Đổi tên một tab trong bảng tính là script của bạn âm thầm hỏng ngay. Một đoạn kiểm tra hai dòng sẽ ngăn điều đó:
function readData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Data');
if (!sheet) {
Logger.log('Sheet "Data" not found');
return;
}
const value = sheet.getRange(1, 1).getValue();
Logger.log(value);
}
Lưu ý: getSheetByName() trả về null, không phải undefined. Điều đó không quan trọng — null.getValue() vẫn ném ra cùng lỗi đó. Điều kiện if (!sheet) bắt được cả hai trường hợp.
Cách sửa 2: Khắc phục lệch một đơn vị khi dùng getValues() để lặp
Đây là bẫy kinh điển. getValues() trả về mảng đánh chỉ số từ 0, nhưng vòng lặp bên dưới bắt đầu từ i = 1 — nên ở vòng lặp cuối, data[lastRow] đã vượt quá cuối mảng:
// SAI: bắt đầu vòng lặp từ 1, nhưng chỉ số mảng chạy từ 0 đến lastRow-1
function brokenLoop() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
const data = sheet.getRange(1, 1, lastRow, 3).getValues();
for (let i = 1; i {
row.forEach((cell, colIndex) => {
Logger.log(`[${rowIndex}][${colIndex}]: ${cell}`);
});
});
}
Cách sửa 4: Kiểm tra kết quả trung gian trong chuỗi gọi hàm
findNext() trả về null khi không tìm thấy gì. Gọi thẳng .getValue() tiếp theo và bạn sẽ bị crash mỗi khi từ khóa không có trong sheet:
// SAI: giả định từ khóa luôn tồn tại trong sheet
function findAndRead() {
const sheet = SpreadsheetApp.getActiveSheet();
const found = sheet.createTextFinder('keyword').findNext();
const value = found.getValue(); // null.getValue() → TypeError
}
// ĐÃ SỬA
function findAndReadSafe() {
const sheet = SpreadsheetApp.getActiveSheet();
const found = sheet.createTextFinder('keyword').findNext();
if (!found) {
Logger.log('Keyword not found in sheet');
return;
}
const value = found.getValue();
Logger.log(value);
}
Cách sửa 5: Truy cập vùng tên (named range) chưa được định nghĩa
getRangeByName() âm thầm trả về null cho bất kỳ tên vùng nào chưa được định nghĩa trong bảng tính. Luôn kiểm tra trước khi đọc:
// SAI
function readNamedRange() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const range = ss.getRangeByName('MyConfig'); // null nếu chưa định nghĩa
const value = range.getValue(); // TypeError
}
// ĐÃ SỬA
function readNamedRangeSafe() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const range = ss.getRangeByName('MyConfig');
if (!range) {
Logger.log('Named range "MyConfig" does not exist');
return;
}
const value = range.getValue();
Logger.log(value);
}
Mẹo debug: ghi log biến trước khi xảy ra lỗi
Stack trace trong Apps Script đôi khi khá mơ hồ. Khi bạn không chắc biến nào đang là undefined, hãy thêm dòng log ngay trước lệnh gây lỗi:
Logger.log('sheet: ' + sheet);
Logger.log('range: ' + range);
Logger.log('data type: ' + typeof data);
Mở Xem → Nhật ký (hoặc nhấn Ctrl+Enter trong trình soạn thảo script) và bạn sẽ thấy chính xác biến nào in ra null hoặc undefined trước khi crash. Cách này nhanh hơn nhiều so với đọc stack trace.
Kiểm tra sau khi sửa
Sau khi áp dụng bản sửa lỗi, hãy xác nhận nó thực sự hoạt động:
- Chạy hàm thủ công qua Chạy → Chạy hàm trong trình soạn thảo Apps Script.
- Mở Xem → Lần thực thi — một lần chạy thành công sẽ không có dòng nào màu đỏ.
- Kiểm tra các trường hợp biên một cách chủ động: sheet rỗng, tab bị đổi tên, vùng không có dữ liệu. Đây chính xác là những điều kiện đã làm lộ ra lỗi.
- Đang dùng trigger theo thời gian? Kiểm tra Xem → Lần thực thi sau lần chạy theo lịch tiếp theo để xác nhận không có lỗi âm thầm nào xảy ra.
Phòng ngừa
- Coi
getSheetByName(),getRangeByName()vàfindNext()là các hàm có thể trả về null. Luôn thêmif (!result) return;trước khi dùng kết quả của chúng. - Điều khiển vòng lặp bằng
data.length, không phải số hàng thô lấy từ sheet. Mảng đã biết chính xác nó có bao nhiêu hàng. - Ghi nhớ rõ quy tắc đánh chỉ số: hàng và cột trên sheet bắt đầu từ 1, chỉ số mảng bắt đầu từ 0. Đừng bao giờ dùng nhầm hệ thống này cho hệ thống kia.
- Đặt
if (lastRow < 1) return;ở đầu bất kỳ hàm nào đọc dữ liệu sheet. Chỉ tốn một dòng nhưng ngăn được cả một nhóm lỗi.

