在寫 Apps Script 操作 Google 試算表時,你可能也遇過這種怪事:明明剛把值寫進去,下一步讀回來卻空空的;或是狀態欄位更新了,畫面卻像在放空。
其實不一定是你程式寫壞了,而是試算表會把多個動作「先排隊、後處理」,為了省時間會延後真正寫回。這時候,SpreadsheetApp.flush() 就派上用場了。
它的工作很樸實——把所有待處理的更動立刻套用,讓你接著的讀取或視覺更新可以基於「最新」狀態。
本篇文章會用日常情境講清楚:什麼時候該用、不該用、怎麼用才不拖慢速度,還會整理常見坑(例如以為 flush 等於等公式重算)與實戰範例,讓你在「寫→讀→顯示」的節點更穩、更可預期。希望本篇文章能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼需要 flush()
Apps Script 在對試算表做多次改動時,會「聰明地」把多個操作打包、延後實際寫回,以減少往返與提升速度。多數情況你不必管它;但當你緊接著要讀回剛寫入的值、更新圖表或做權限鎖釋放前的最後提交,就需要手動呼叫 SpreadsheetApp.flush(),把所有排隊的更動立刻送進工作表。
flush() 是什麼(官方定義+工作原理)
官方定義:
flush() 會套用所有待處理的試算表更動(Applies all pending Spreadsheet changes)。
工作原理:
Apps Script 對試算表有「延遲寫回(write-back)」與「批次最佳化」;flush() 會強制把緩存中的更動推送到表格,讓接下來的讀取與 UI 狀態同步在最新的資料上。
小提醒:
flush() 只保證寫入已落地;不保證公式已完成重算。若你的流程仰賴公式結果,仍可能需要等待或輪詢確認(下文有範例)。社群經驗也指出,單靠 flush() 並不足以等待複雜計算完成。
何時該用/不該用(判斷清單)
該用 flush() 的情境
寫入後立刻讀取:例如 setValues() 之後馬上 getValues() 作邏輯判斷。
即時顯示給使用者:
腳本執行中更新狀態欄位(如「處理中 45%」),希望使用者當下就看到。
釋放鎖之前:
多執行緒(onEdit/定時觸發器)情境,釋放 LockService 的鎖前先 flush(),避免別的執行緒讀到半套資料。
不必或不該用的情境
每次迴圈都 flush():
會嚴重拖慢速度,增加 API 往返與配額壓力。應改用批次寫入(例如一次 setValues())。
自訂函式(=myFunc())想改別的儲存格:
就算 flush() 也無法繞過限制;自訂函式只能回填自身或相鄰範圍,不能隨意改表上其他地方。
等待公式重算:
flush() 不是「等重算」,必要時要自行輪詢或加短暫 sleep() 再檢查條件。
基本語法與最小可用範例
function writeThenRead() {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName('工作表1');
sheet.getRange(1, 1).setValue('Hello');
SpreadsheetApp.flush(); // 立刻寫回
const val = sheet.getRange(1, 1).getValue(); // 現在能讀到最新值
Logger.log(val);
}
註:SpreadsheetApp.flush() 無參數、無回傳值;呼叫即生效。
逐步操作教學(4 個情境實戰)
情境 A:寫入後馬上讀回做判斷
目標:匯入資料→立即讀回總筆數→若超過門檻則分頁。
步驟:
1. 在試算表新增工作表「原始資料」「拆分A」「拆分B」。
2. 開啟 擴充功能 → Apps Script。
3. 貼上程式並執行 importAndSplit(),第一次會要求授權。
4. 回到表格觀察結果。
function importAndSplit() {
const sheet = SpreadsheetApp.getActive().getSheetByName('原始資料');
// 模擬寫入 2,000 筆
const values = Array.from({length: 2000}, (_, i) => [i + 1, `item-${i+1}`]);
sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
// 關鍵:馬上讀回前先 flush
SpreadsheetApp.flush(); // 套用所有待處理寫入 :contentReference[oaicite:12]{index=12}
const lastRow = sheet.getLastRow();
if (lastRow - 1 > 1000) {
splitToSheets_('原始資料', ['拆分A', '拆分B']);
}
}
function splitToSheets_(srcName, targets) {
const ss = SpreadsheetApp.getActive();
const source = ss.getSheetByName(srcName);
const rows = source.getLastRow() - 1;
const half = Math.ceil(rows / 2);
const data = source.getRange(2,1,rows,2).getValues();
ss.getSheetByName(targets[0]).getRange(2,1,half,2).setValues(data.slice(0, half));
ss.getSheetByName(targets[1]).getRange(2,1,rows - half,2).setValues(data.slice(half));
SpreadsheetApp.flush();
}
情境 B:寫入公式→等待重算→讀回結果
flush() 會把公式寫入,但不保證重算完成。做法是:flush() 後以短暫延遲+條件輪詢確認目標儲存格已呈現預期格式或值,再繼續流程。
function writeFormulaAndWait() {
const sheet = SpreadsheetApp.getActive().getSheetByName('工作表1');
sheet.getRange('B2').setValue('=SUM(A2:A2001)');
SpreadsheetApp.flush(); // 先確保公式已落地
// 最多等 5 秒,直到 B2 不是空白/不是 # 開頭錯誤
const deadline = Date.now() + 5000;
while (Date.now() < deadline) {
const v = sheet.getRange('B2').getDisplayValue();
if (v && !String(v).startsWith('#')) break;
Utilities.sleep(200);
}
const result = sheet.getRange('B2').getValue();
Logger.log('總和:' + result);
}
情境 C:多執行緒(onEdit / 定時)+鎖控管
多個觸發器同時寫表,容易互相覆蓋。建議用 LockService 取得鎖 → 寫入 → flush() → 釋放鎖,確保別的執行緒看到的是一致狀態。官方也建議在處理試算表時釋放鎖前呼叫 flush()。
function onEdit(e) {
const lock = LockService.getDocumentLock();
lock.waitLock(10_000);
try {
const sheet = e.range.getSheet();
sheet.getRange('F1').setValue(new Date()); // 更新最後修改時間
SpreadsheetApp.flush(); // 在釋放鎖前提交所有更動 :contentReference[oaicite:15]{index=15}
} finally {
lock.releaseLock();
}
}
情境 D:執行期間即時顯示進度
把進度寫到某格,flush() 讓使用者在畫面上即時看到更新(例如處理一萬筆時每 5% 更新一次)。
function progressDemo() {
const sheet = SpreadsheetApp.getActive().getSheetByName('工作表1');
const total = 10000;
for (let i = 1; i <= total; i++) {
// ...處理第 i 筆...
if (i % 500 === 0) {
sheet.getRange('H1').setValue(`處理中… ${(i/total*100).toFixed(0)}%`);
SpreadsheetApp.flush(); // 讓使用者馬上看到進度
}
}
sheet.getRange('H1').setValue('完成');
SpreadsheetApp.flush();
}
常見錯誤與雷點
1. 把 flush() 當「存檔」
不是。Apps Script 自動保存腳本;flush() 是把對試算表的變更「立即套用」。
2. 每寫一次就 flush(),效能崩
在迴圈內逐格寫+逐次 flush() 會很慢,也可能更容易撞到配額與超時。請改成批次收集→一次 setValues()。官方最佳實務也強調避免逐格操作。
3. 自訂函式想改別處,指望 flush() 幫忙
無效。自訂函式不能任意改動其他儲存格(只能回填自身/相鄰範圍),這是引擎級限制。
4. 以為 flush() 等於「等公式算完」
不是。若需要確定結果可用,請加短暫等待並檢查條件(如非空、非錯誤字串),或設計流程避免立刻依賴複雜公式結果。
5. 跨檔寫讀的順序混亂
SpreadsheetApp.flush() 針對當前檔案的變更生效。若你同時用 openByUrl()/openById() 操作不同檔案,請分別在各自操作批次後呼叫一次,確保上下文正確。(此為實務建議;避免在多文件間交錯寫讀。)
6. 忽略鎖導致競態
多觸發器/多人同時啟動腳本時,未用鎖就 flush(),仍可能互相踩到。請用 LockService 先鎖住、寫入、flush()、再釋放。官方文件明確建議此順序。
效能最佳化:搭配批次寫入與「少 flush、但用得準」
1. 一次取出/一次寫入:
把要寫入的資料先放在二維陣列,用單次 setValues() 落地;盡量避免逐格 setValue()。
2. 把 flush() 放在「邏輯分段點」:
例如每一萬筆一段、或「寫完→讀回」的切換點。
3. 需要多類更動時,分批組塊:
格式變更、資料寫入、保護範圍等先各自批次處理,再於段落尾端 flush()。
4. Sheets API 的批次更新:
若你走 Advanced Sheets API,亦可用 spreadsheets.batchUpdate 合併多種請求,降低往返成本。
問題集
Q1:我什麼時候「一定」要加 flush()?
A:寫完就要讀、要給使用者立刻看到畫面變化、或釋放鎖前。其它時候盡量少用。
Q2:加很多次 flush() 不是更安全嗎?
A:安全未必增加,效能一定下降,還可能更容易撞到配額/時限。請用「少量且關鍵點」的呼叫,並配合批次寫入。
Q3:自訂函式裡 flush() 可以讓我改別的儲存格嗎?
A:不行。這是自訂函式規範,與是否 flush() 無關。
Q4:flush() 能保證公式算完嗎?
A:不能;它只保證寫入落地。需要就加短暫等待+條件檢查。
