很多人第一次用 Google Apps Script 的 DriveApp 都會以為回傳的是陣列,結果一呼叫 map() 或直接取值就報錯。其實像 getFiles()、getFolders() 拿到的是「迭代器」,要先 hasNext() 再 next() 一個一個取。差在這裡,寫法就完全不一樣。這篇會用生活化的方式把 next() 講清楚:它到底做什麼、什麼情境最好用、常見踩雷(像沒判斷就硬 next()、查詢語法寫錯、效能被時間限制卡住)、以及怎麼安全地把結果轉成陣列方便後續處理。文中也會補上實戰片段:指定資料夾篩檔、批次改名、遞迴走訪子資料夾,還有長任務怎麼用續跑 token 接力。希望本篇文章的內容可以幫助到您。
目錄
{tocify} $title={目錄}
next() 是什麼?為什麼不是回傳陣列?
在 DriveApp 中,像 DriveApp.getFiles()、getFilesByName(name)、getFilesByType(mimeType)、Folder.getFiles() 與 getFolders() 等方法,回傳的都是 迭代器,不是陣列。你不能直接用 forEach、map 去跑;正確寫法是:
const it = DriveApp.getFiles(); // FileIterator
while (it.hasNext()) {
const file = it.next(); // 逐一取出 File 物件
Logger.log(file.getName());
}
hasNext():回傳布林值,表示還有沒有下一個。
next():取出下一個元素;如果已經沒有,會丟出例外(Exception)。官方對 FileIterator 與 FolderIterator 的說明一致:沒有剩餘項目時呼叫 next() 就會拋錯,因此務必先判斷。
這種設計是為了能處理「可能很大」的集合,不需要一次載入所有項目到記憶體。DriveApp 本身也提供多種取回迭代器的入口,例如 DriveApp.getFiles()、DriveApp.getFilesByName(name)、DriveApp.getFilesByType(mimeType),與從 Folder 物件上取得的 getFiles() / getFolders() 等。
你會在哪些情境用到 next()?
1. 列出整個雲端硬碟的所有檔案
DriveApp.getFiles() → FileIterator → while (hasNext()) next()
2. 在特定資料夾中逐一處理檔案
someFolder.getFiles() → FileIterator → next() 取出 File
3. 用條件搜尋再處理(DriveApp 的簡易查詢)
DriveApp.searchFiles(query)(回 FileIterator),搭配 hasNext()/next() 走訪。想更彈性排序或分頁,需改用「進階服務 Drive」或 REST API。
4. 層層往下走資料夾樹
Folder.getFolders() 會回 FolderIterator,用 next() 取得 Folder,再各自 getFiles()。
標準使用步驟(安全、穩定、不會拋錯)
模式 A:處理檔案迭代器
function listFilesBasic() {
const files = DriveApp.getFiles(); // FileIterator
while (files.hasNext()) {
const f = files.next(); // File
Logger.log([f.getId(), f.getName(), f.getMimeType()].join(' | '));
}
}
模式 B:處理資料夾內檔案
function listFilesInFolder(folderId) {
const folder = DriveApp.getFolderById(folderId);
const it = folder.getFiles(); // FileIterator
while (it.hasNext()) {
const file = it.next();
// 在這裡執行你的邏輯,例如搬移、改名、加共用…
}
}
模式 C:條件搜尋(名稱、MIME 類型、時間)
function searchSpreadsheetsWithKeyword(keyword) {
const query = "title contains '" + keyword.replace(/'/g, "\\'") + "'" +
" and mimeType = 'application/vnd.google-apps.spreadsheet'";
const it = DriveApp.searchFiles(query); // FileIterator
while (it.hasNext()) {
const file = it.next();
Logger.log(file.getName());
}
}
注意 title contains 在某些情況下僅做前綴詞匹配的行為觀察(「HelloWorld」可配到「Hello」,但不一定配得到「World」),撰寫查詢時要有心理準備。要更精準與可排序,建議改用 Drive API 的 files.list。
常見錯誤與雷點
1. 在沒有 hasNext() 的情況下直接呼叫 next()
症狀:拋出 Exception(沒有剩餘項目)。
修正:一律寫成 while (it.hasNext()) { const x = it.next(); ... }。這不是可選步驟,是必要保護。官方文件明確指出:沒有剩餘項目時呼叫 next() 會拋錯。
2. 以為 FileIterator/FolderIterator 能 map / forEach
症狀:TypeError 或邏輯跑不起來。
原因:它不是陣列。
修正:用 while + next();要陣列,就自己 push:
const arr = [];
while (it.hasNext()) arr.push(it.next());
(也有人在討論中提醒:只能一個一個 next() 取,不能直接用 map/filter,這是迭代器限制。)
3. 取得空迭代器、hasNext() 永遠是 false
症狀:你確定有檔案,但 getFilesByName / searchFiles 卻取不到。
排查清單:
名稱是否完全符合(getFilesByName 是完全比對,不是包含比對)。
查詢語法是否正確(title contains 'X' and mimeType = 'Y';需要 and not title contains 'Archived' 這種語法時,and 放的位置要對)。
檔案是不是其實在別的資料夾或你沒有權限。
遇到零星案例時,改以 Drive API files.list 驗證結果。
社群上偶有個案貼文反映 getFilesByName() 對極少數檔案取不到,這類非系統性問題可用 API 交叉驗證並改以 files.list(q=...) 取得。
4. 誤以為能要求排序或自訂分頁
現況:DriveApp 的迭代器不提供排序參數與顯式分頁控制;只管「下一個」。
對策:
需要排序(依 modifiedTime、name 等)或精確分頁,請改用「進階服務 Drive」或直接呼叫 Drive REST 的 files.list(可加 orderBy、q、pageSize、pageToken)。
5. 忘了權限範圍(Scopes)
症狀:第一次執行就跳要求授權或拿不到內容。
說明:
像 DriveApp.getFiles()、getFilesByName() 等都屬於需要 drive.readonly 或 drive 授權範圍。第一次跑腳本會要求同意。
6. 大量資料的效能與時間限制
狀況:你的雲端硬碟很大;腳本執行時間(6 分鐘區間)吃緊。
對策:
迭代時儘量早判斷早跳出(例如先快速比對 MIME 或名稱就跳過)。
長任務切批次,搭配持續化狀態:FolderIterator 提供 getContinuationToken(),可保存進度,下次接續。
需要可控分頁時,改用 Drive API files.list 的 pageToken 機制。
實務範例:你可以直接複製改名就上線
範例 1:列出整個雲端硬碟中前 200 個 PDF 的名稱與連結(遇到很多就提早停)
function listSomePDFs(limit = 200) {
const it = DriveApp.getFilesByType('application/pdf');
let count = 0;
while (it.hasNext() && count < limit) {
const f = it.next();
Logger.log(`${f.getName()} | https://drive.google.com/open?id=${f.getId()}`);
count++;
}
}
為何可行:getFilesByType(mimeType) 回 FileIterator,next() 逐一取出 File。
範例 2:在指定資料夾中搜尋標題含關鍵字的 Google 試算表,並加上星號
function starSheetsInFolder(folderId, keyword) {
const folder = DriveApp.getFolderById(folderId);
const q = "title contains '" + keyword.replace(/'/g, "\\'") + "'" +
" and mimeType = 'application/vnd.google-apps.spreadsheet'";
const it = folder.searchFiles(q); // FileIterator
while (it.hasNext()) {
const file = it.next(); // File
file.setStarred(true);
}
}
小叮嚀:title contains 的比對在某些情況下類似「前綴」行為;若要更精準/能排序,請考慮 Drive API。
範例 3:安全取得「唯一名稱」的資料夾,若無就建立
function getOrCreateFolderByExactName(name) {
const it = DriveApp.getFoldersByName(name); // FolderIterator
if (it.hasNext()) return it.next(); // 已存在 → 直接用
return DriveApp.createFolder(name); // 不存在 → 建立
}
說明:getFoldersByName() 回 FolderIterator,先 hasNext() 再 next() 取第一個。
範例 4:深度走訪:遞迴列出某資料夾下所有試算表
function listAllSheetsUnderFolder(rootId) {
const root = DriveApp.getFolderById(rootId);
walk(root);
function walk(folder) {
const files = folder.getFiles();
while (files.hasNext()) {
const f = files.next();
if (f.getMimeType() === 'application/vnd.google-apps.spreadsheet') {
Logger.log(`[SHEET] ${f.getName()} | ${f.getId()}`);
}
}
const subs = folder.getFolders();
while (subs.hasNext()) {
walk(subs.next());
}
}
}
兩種迭代器交替使用:FileIterator 與 FolderIterator。每次都由 hasNext() 把關,next() 只在保證有下一個時呼叫。
範例 5:長任務續跑(使用 getContinuationToken())
function processInBatches() {
const props = PropertiesService.getScriptProperties();
const token = props.getProperty('FOLDER_TOKEN');
let it = token
? DriveApp.continueFolderIterator(token)
: DriveApp.getRootFolder().getFolders();
let processed = 0;
while (it.hasNext() && processed < 50) { // 每次跑 50 個
const folder = it.next();
// ...對 folder 做事...
processed++;
}
// 存續標記,下一輪接續
if (it.hasNext()) {
props.setProperty('FOLDER_TOKEN', it.getContinuationToken());
} else {
props.deleteProperty('FOLDER_TOKEN');
}
}
FolderIterator 支援取得延續用的 token,以利批次化處理。
與 Drive API 的分工:什麼時候該放下 DriveApp?
DriveApp + next() 的優勢是簡單好上手;但它的迭代器 無排序、無 pageSize、無複雜查詢。當你需要:
依 modifiedTime desc 排序;
指定每頁 100 筆並拿 pageToken 分頁;
用更完整的 q 語法(擁有者、共用狀態、parents in …);
就該改用 Drive API files.list(可透過「進階服務 Drive」或直接呼叫 REST),這時一樣是 while 迴圈處理結果,但基礎能力更強、效率更可控。
Debug 與排錯清單
1. 永遠先 hasNext():沒有就別 next()。
2. 確認來源:DriveApp 取到的是整庫;Folder 取到的是該資料夾;別混淆。
3. 查詢字串:title contains、mimeType、modifiedDate、and not 等語法位置要對(常見是少了 and)。
4. 權限:首次執行需要 drive.readonly / drive 授權。
5. 需求超過 DriveApp 能力:要排序或嚴格分頁,就上 Drive API。
6. 極端個案:getFilesByName 找不到少數已存在檔案時,用 files.list 交叉驗證。
問題集
Q1:能不能把迭代器「一次展開」成陣列?
A:可以,但要自己 while 取完 push 到陣列;迭代器本身沒有 toArray()。
Q2:為什麼我覺得 title contains 有時不中?
A:社群觀察到它在部分情境像前綴匹配;要更一致用法與排序,請改 Drive API。
Q3:能不能指定排序(例如名稱或修改時間)?
A:DriveApp 不行;Drive API 可用 orderBy。
可直接套用的程式片段(拷貝就能用)
片段 A:把某資料夾下所有 PDF 搬到另一個資料夾
function movePdfs(srcFolderId, dstFolderId) {
const src = DriveApp.getFolderById(srcFolderId);
const dst = DriveApp.getFolderById(dstFolderId);
const it = src.getFilesByType('application/pdf');
while (it.hasNext()) {
const f = it.next();
dst.addFile(f);
src.removeFile(f);
}
}
片段 B:找名稱完全相同的一批檔案並重新命名(避免重名)
function renameDuplicateNames(name) {
const it = DriveApp.getFilesByName(name);
let i = 1;
while (it.hasNext()) {
const f = it.next();
f.setName(`${name} (${i++})`);
}
}
片段 C:用查詢字串篩出近 7 天更新的試算表
function recentSheets(days = 7) {
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
.toISOString().slice(0, 19) + 'Z';
const q = "mimeType = 'application/vnd.google-apps.spreadsheet' " +
"and modifiedDate >= '" + since + "'";
const it = DriveApp.searchFiles(q);
while (it.hasNext()) {
const f = it.next();
Logger.log(`${f.getName()} | ${f.getLastUpdated()}`);
}
}
小結
1. next() 是 DriveApp 迭代器的核心用法,一定先 hasNext() 再 next(),不然就會拋錯。
2. DriveApp 的迭代器主打「簡單走訪」,不負責排序與分頁;想要更強查詢、排序與分頁,改用 Drive API files.list。
3. 針對大量資料與長任務,善用提早跳出與 getContinuationToken() 的續跑設計;必要時換 API。
4. 查詢語法細節(特別是 title contains 行為)要小心,碰到不確定的匹配與排序,用 API 交叉驗證。
