簡體   English   中英

如何解決 Google Apps 腳本開發中的常見錯誤

[英]How to solve common errors in Google Apps Script development

問答目前是討論的主題,請參與。 目前的計划是盡可能分成問答環節。 A&A 的答案是社區 wiki,當狀態得到解決時,問題應該成為一個問題。


前言

本問答力求成為 Google Apps Script 語言開發過程中遇到的常見錯誤的集合和參考目標,以期提高標簽的長期可維護性。

There are several similar and successful undergoings in other languages and general-purpose tags (see c++ , android , php , php again ), and this one follows suit.


為什么存在?

新開發人員和經驗豐富的開發人員提出的關於開發和生產過程中遇到的錯誤的含義和解決方案的問題數量很多,可以有效地簡化為一個答案。 在撰寫本文時,即使僅通過語言標簽運行查詢也會產生:

  • “找不到方法” 8 頁
  • “無法讀取屬性” 9 頁
  • “在這種情況下無法調用...” 5 頁
  • “您沒有權限” 11 頁

由於需要考慮細微差別以及通常措辭不佳的標題,因此鏈接到最相關的副本對於志願者來說既困難又耗時。


它由什么組成?

本問答包含的條目旨在提供有關如何:

  • 解析錯誤消息結構
  • 了解錯誤的含義
  • 持續復制(如適用)
  • 解決問題
  • 提供規范問答的鏈接(如果可能)

目錄

為了幫助您瀏覽不斷增長的參考資料,請使用以下目錄:

  1. 一般錯誤
  2. 特定於服務的錯誤

這不是什么?

Q&A 中的 scope 僅限於常見(並非微不足道)。 這不是:

  • 包羅萬象的指南或“最佳實踐”集合
  • 一般 ECMAScript 錯誤的參考
  • 氣體文件
  • 資源列表(我們有一個標簽 wiki

要添加什么?

添加條目時,請考慮以下事項:

  • 錯誤是否足夠普遍(參見“為什么”部分的示例)?
  • 解決方案能否簡明扼要地描述並適用於大多數情況?

前言

答案提供了使用任何 Google 服務(內置和高級)或 API 時可能遇到的一般錯誤的指南。 對於特定於某些服務的錯誤,請參閱其他答案

返回參考


一般錯誤


信息

TypeError:無法從未undefined (or null)中讀取屬性“ property name here

描述

該錯誤消息表明您正在嘗試訪問Object實例上的屬性,但在運行時,變量實際保存的值是一種特殊的數據類型undefined 通常,在訪問 object 的嵌套屬性時會發生錯誤。

使用數值代替屬性名稱的此錯誤的變體表明需要Array的實例。 由於 JavaScript 中的 arrays 是對象,因此這里提到的所有內容也都是關於它們的。

動態構造的對象有一種特殊情況,例如僅在特定上下文中可用的事件對象,例如向應用程序發出 HTTP 請求或通過時間或基於事件的觸發器調用 function。

錯誤是TypeError ,因為預期的是"object" ,但收到了"undefined"

怎么修

  1. 使用默認值
    邏輯或|| JavaScript 中的運算符具有一個有趣的屬性,即如果左側為falsy則評估右側。 由於 JS 中的對象是真實的,而undefinednull是虛假的,因此像(myVar || {}).myProp [ (myVar || [])[index] for arrays] 這樣的表達式將保證不會拋出錯誤並且屬性至少是undefined的。

    還可以提供默認值: (myVar || { myProp: 2 })保證訪問myProp默認返回2 arrays: (myVar || [1,2,3])也是如此。

  2. 檢查類型
    對於特殊情況尤其如此, typeof 運算符結合if 語句比較運算符將允許 function 在其指定上下文之外運行(即用於調試目的)或引入分支邏輯,具體取決於 object 是否存在。

    可以控制檢查的嚴格程度:

  • lax ("not undefined"): if(typeof myVar;== "undefined") { //do something; } if(typeof myVar;== "undefined") { //do something; }
  • strict ("proper objects only"): if(typeof myVar === "object" && myVar) { //do stuff }

相關問答

  1. 解析 GAS 項目的順序作為問題的來源

信息

無法將some value轉換為data type

描述

由於傳遞的參數類型與方法預期的不同,因此引發錯誤。 導致錯誤的常見錯誤是將數字意外強制轉換為字符串

如何重現

function testConversionError() {
  const ss = SpreadsheetApp.getActiveSheet();
  ss.getRange("42.0",1);
}

怎么修

確保錯誤消息中引用的值是文檔要求的數據類型,並根據需要進行轉換


信息

無法從此上下文中調用Service and method name

描述

此錯誤發生在上下文不匹配且特定於容器綁定腳本的情況下。 導致錯誤的主要用例是嘗試從另一種(即電子表格中的DocumentApp.getUi()調用僅在一種文檔類型中可用的方法(通常是getUi() ,因為它由多個服務共享)。

次要但也是突出的情況是調用未明確允許自定義 function (通常是由特殊 JSDoc 樣式注釋@customfunction並用作公式的 function )調用的服務的結果。

如何重現

對於綁定腳本上下文不匹配,請在與 Google 表格(或 Google 文檔以外的任何內容)相關的腳本項目中聲明並運行此 function:

function testContextMismatch() {
  const doc = DocumentApp.getUi();
}

請注意,調用DocumentApp.getActiveDocument()只會導致null不匹配,並且執行將成功

對於自定義函數,使用下面在任何單元格中聲明的 function 作為公式:

/**
 * @customfunction
 */
function testConversionError() {
  const ui = SpreadsheetApp.getUi();
  ui.alert(`UI is out of scope of custom function`);
}

怎么修

  1. 上下文不匹配很容易通過更改調用該方法的服務來解決。
  2. 無法使用自定義功能來調用這些服務,使用自定義菜單或對話框

信息

Method name here

參數param namesmethod name的方法簽名不匹配

描述

這個錯誤對於新手來說是一個臭名昭著的令人困惑的信息。 它說的是在調用相關方法時傳遞的一個或多個 arguments 中發生類型不匹配

沒有與您的調用方式相對應的簽名方法,因此“未找到”

怎么修

此處唯一的解決方法是仔細閱讀文檔並檢查參數的順序和推斷類型是否正確(使用具有自動完成功能的良好 IDE 會有所幫助)。 但是,有時會發生問題,因為人們期望值是某種類型,而在運行時它是另一種類型。 有幾個技巧可以防止此類問題:

  1. 設置類型保護( typeof myVar === "string"和類似的)。
  2. 由於 JavaScript 是動態類型的,因此添加了一個驗證器來動態修復類型

樣本

 /** * @summary pure arg validator boilerplate * @param {function (any): any} * @param {...any} args * @returns {any[]} */ const validate = (guard, ...args) => args.map(guard); const functionWithValidator = (...args) => { const guard = (arg) => typeof arg?== "number": parseInt(arg); arg, const [a,b,c] = validate(guard. ..;args), const asObject = { a, b; c }. console;log(asObject); return asObject; }, //driver IIFE (() => { functionWithValidator("1 apple",2;"0x5"); })()


留言

您無權執行該操作

該腳本無權執行該操作

描述

該錯誤表明訪問的 API 或服務之一缺乏來自用戶的足夠權限。 在其文檔中具有授權部分的每個服務方法都需要至少一個要授權的范圍。

由於 GAS 本質上為方便開發而封裝了 Google API,因此可以使用 OAuth 2.0 范圍內的 API 參考范圍中列出的大多數范圍,但如果在相應的文檔中列出了一個范圍,則使用它可能會更好,因為存在一些不一致之處。

請注意,自定義函數在未經授權的情況下運行。 從 Google 表格單元格調用 function 是導致此錯誤的最常見原因。

怎么修

如果從腳本編輯器運行調用服務的 function,系統會自動提示您使用相關范圍對其進行授權。 盡管對快速手動測試很有用,但最好在應用程序清單 (appscript.json) 中明確設置范圍 此外,如果打算發布應用程序,自動范圍通常太寬而無法通過審核

清單文件中的oauthScopes字段(如果在代碼編輯器中,則View -> Show manifest file )應如下所示:

  "oauthScopes": [
    "https://www.googleapis.com/auth/script.container.ui",
    "https://www.googleapis.com/auth/userinfo.email",
    //etc
  ]

對於自定義功能,您可以通過切換到從菜單或按鈕調用function來修復它, 因為自定義功能無法授權

對於那些開發編輯器插件的人來說,這個錯誤意味着一個未處理的授權生命周期模式:如果 auth 模式是AuthMode.NONE ,則必須在調用需要授權的服務之前中止。

相關原因及解決方法

  1. @OnlyCurrentDoc 限制腳本訪問scope
  2. 范圍自動檢測

信息

ReferenceError:未定義service name

描述

最常見的原因是使用高級服務而不啟用它。 啟用此類服務后,將指定標識符下的變量附加到全局 scope ,開發人員可以直接引用該變量。 因此,當引用禁用的服務時,會引發ReferenceError

怎么修

Go 到“資源 -> 高級 Google 服務”菜單並啟用引用的服務。 請注意,標識符應等於引用的全局變量。 更詳細的解釋,請閱讀官方指南

如果沒有引用任何高級服務,則錯誤指向一個未聲明的變量被引用。


信息

腳本完成但沒有返回任何內容。

找不到腳本 function: doGet or doPost

描述

這本身不是錯誤(因為返回的 HTTP 響應代碼為200並且執行被標記為成功,但通常被認為是一個錯誤。嘗試從瀏覽器發出請求/訪問腳本時出現該消息部署為 Web應用程序

發生這種情況的主要原因有兩個:

  1. 沒有doGetdoPost觸發器 function
  2. 上面的觸發器不返回HtmlOutputTextOutput實例

怎么修

對於第一個原因,只需提供doGetdoPost觸發器(或兩者)function。 其次,確保您的應用程序的所有路由都以創建TextOutputHtmlOutput結束:

//doGet returning HTML
function doGet(e) {
  return HtmlService.createHtmlOutput("<p>Some text</p>");
}

//doPost returning text
function doPost(e) {
  const { parameters } = e;
  const echoed = JSON.stringify(parameters);
  return ContentService.createTextOutput(echoed);
}

請注意,應該只聲明一個觸發器 function - 將它們視為應用程序的入口點。

如果觸發器依賴parameter / parameters來路由響應,請確保請求 URL 的結構為“ baseURL /exec? query ”或“ baseURL /dev? query ”,其中query包含要傳遞的參數

相關問答

  1. 聲明觸發器后重新部署

信息

很抱歉,發生服務器錯誤。 請稍等,然后重試。

描述

這是最神秘的錯誤,幾乎可以在任何服務的任何時候發生(盡管DriveApp的使用特別容易受到它的影響)。 該錯誤通常表明谷歌方面的問題在幾個小時/幾天內消失或在此過程中得到修復。

怎么修

對於那個沒有靈丹妙葯,通常,除了在問題跟蹤器上提交問題或聯系支持人員(如果您有 GSuite 帳戶)之外,您無能為力。 在這樣做之前,可以嘗試以下常見的補救措施:

  1. 對於綁定腳本 - 創建新文檔並復制現有項目和數據。
  2. 切換到使用高級Drive服務(始終記得先啟用它)。
  3. 如果錯誤指向帶有 1 的行, 則正則表達式可能存在問題

不要 bash 反對這個錯誤 - 嘗試定位受影響的代碼、文件或為問題加注星標並繼續


沒有明顯問題的語法錯誤

此錯誤可能是由於在使用已棄用的 V8 運行時(在編寫 GAS 平台時使用 V8時)使用 ES6 語法(例如,箭頭函數)引起的。

怎么修

打開 "appscript.json" 清單文件並檢查runtimeVersion是否設置為"V8" ,如果沒有則更改,否則刪除任何ES6 功能


配額相關錯誤

有幾個與對服務使用的配額有關的錯誤。 谷歌有一個完整的列表,但作為一般的經驗法則,如果一條消息匹配“太多”模式,你很可能已經超出了相應的配額。

最可能遇到的錯誤:

  • 服務調用次數過多: service name
  • 運行的腳本太多
  • 一天使用過多計算機時間的服務
  • 此腳本的觸發器過多

怎么修

在大多數情況下,唯一的解決方法是等到配額刷新或切換到另一個帳戶(除非腳本部署為 Web 應用程序並具有“以我身份運行”的權限,在這種情況下,所有者的配額將在所有用戶之間共享)。

要引用當時的文檔:

每日配額在 24 小時 window 結束時刷新; 但是,此刷新的確切時間因用戶而異。

請注意,某些服務(例如MailApp )具有諸如getRemainingDailyQuota之類的方法,可以檢查剩余配額。

在超過觸發器的最大數量的情況下,可以通過getProjectTriggers()檢查安裝了多少(或檢查“我的觸發器”選項卡)並采取相應措施減少數量(例如,通過使用deleteTrigger(trigger)擺脫一些中的)。

相關規范問答

  1. 如何應用和更新每日限制?
  2. “超過最大執行時間” 問題
  3. 優化服務調用以減少執行時間

參考

  1. 如何讓錯誤信息更有意義
  2. 調試自定義函數

特定於服務的錯誤

答案涉及與內置服務相關的錯誤。 有關一般參考,請參閱其他答案 歡迎提交解決官方參考中列出的服務問題的條目。

返回參考


電子表格應用程序

范圍內的行數必須至少為 1

此錯誤通常是由調用getRange方法引起的,其中設置行數的參數恰好等於0 如果您依賴getLastRow()調用返回值,請小心 - 僅在非空工作表上使用它( getDataRange會更安全)。

如何重現

sh.getRange(1, 1, 0, sh.getLastColumn()); //third param is the number of rows

怎么修

添加一個防止值變為0的保護就足夠了。 下面的模式默認為包含數據的最后一行(如果您只需要一定數量的行,則為可選),如果也失敗,則為1

//willFail is defined elsewhere
sh.getRange(1, 1, willFail || sh.getLastRow() || 1, sh.getLastColumn());

錯誤:“參考不存在”

不返回值的電子表格單元格中調用自定義 function 時會發生錯誤。 文檔確實只提到了一個“必須返回要顯示的值”,但這里的問題是空數組也不是有效的返回值(沒有要顯示的元素)。

如何重現

在任何 Google 表格電子表格單元格中調用以下自定義 function:

/**
 * @customfunction
 */
const testReferenceError = () => [];

怎么修

無需特殊處理,只需確保length > 0即可。


數據中的行數或單元格數與范圍內的rows or cells rows or cells不匹配。 數據有N但范圍有M

描述

該錯誤表明范圍維度與值的不匹配 通常,當值矩陣小於或大於范圍時,使用setValues()方法會出現問題。

如何重現

function testOutOfRange() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const sh = ss.getActiveSheet();
    const rng = sh.getActiveRange();
    const vals = rng.getValues();
    
    try {
        vals.push([]);
        rng.setValues(vals);
    } catch (error) {
        const ui = SpreadsheetApp.getUi();
        ui.alert(error.message);
    }
}

怎么修

如果通常期望值超出范圍,請實現一個捕捉此類狀態的守衛,例如:

const checkBounds = (rng, values) => {
    const targetRows = rng.getHeight();
    const targetCols = rng.getWidth();

    const { length } = values;
    const [firstRow] = values;

    return length === targetRows &&
        firstRow.length === targetCols;
};

該范圍的坐標在工作表的尺寸之外。

描述

該錯誤是兩個問題之間發生沖突的結果:

  1. Range超出范圍( getRange()不會在請求不存在的范圍時拋出)
  2. 試圖在Range實例上調用引用不存在的工作表維度的方法。

如何重現

function testOB() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getActiveSheet();
  const rng = sh.getRange(sh.getMaxRows() + 1, 1);
  rng.insertCheckboxes();
}

怎么修

檢查行數 ( getMaxRow() ) 和列數 ( getMaxColumns() ) 是否都大於或等於傳遞給getRange()方法調用的參數,並相應地更改它們。


例外:您不能在已有過濾器的工作表中創建過濾器。

描述

該消息意味着您正在嘗試在已經具有過濾器集(通過 UI 或腳本)的Sheet中的Range上調用createFilter方法,從而違反了對每個Sheet 1 個過濾器的限制,以引用文檔:

一張表中最多可以有一個過濾器。

如何重現

const testFilterExistsError = () => {
  const sh = SpreadsheetApp.getActiveSheet();  
  const rng = sh.getDataRange();
  
  const filter1 = rng.createFilter();
  const filter2 = rng.createFilter();
};

怎么修

添加一個守衛,首先檢查過濾器是否存在。 如果在Range實例上調用getFilter ,則返回過濾器或null並且非常適合該工作:

const testFilterGuard = () => {
  const sh = SpreadsheetApp.getActiveSheet();  
  const rng = sh.getDataRange();
  
  const filter = rng.getFilter() || rng.createFilter();
  //do something useful;
};

UrlFetchApp

無值提供的屬性:url

描述

該錯誤特定於UrlFetchApp服務,並且在使用空字符串或非字符串值調用fetchfetchAll方法時發生。

如何重現

const response = UrlFetchApp.fetch("", {});

怎么修

確保將包含 URI(不一定有效)的字符串作為其第一個參數傳遞給該方法。 由於其常見的根本原因是訪問object 或數組上不存在的屬性,因此請檢查您的訪問器是否返回實際值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM