【问题标题】:How to solve common errors in Google Apps Script development如何解决 Google Apps 脚本开发中的常见错误
【发布时间】:2020-10-01 18:53:46
【问题描述】:

Q&A 目前是meta 讨论的主题,请参与。目前的计划是尽可能分成问答环节。 A&A 的答案是社区 wiki,当状态得到解决时,问题应该成为一个问题。


前言

本问答力求成为Google Apps Script语言开发过程中遇到的常见错误的集合和参考目标,以期提高标签的长期可维护性。

在其他语言和通用标签中也有几个类似的成功经历(参见c++androidphpphp again),这一个也效仿。


它为什么存在?

新开发人员和经验丰富的开发人员提出的关于开发和生产过程中遇到的错误的含义和解决方案的问题数量很多,可以有效地简化为一个答案。在撰写本文时,即使仅通过语言标签运行查询也会产生:

链接到最相关的副本对于志愿者来说既困难又耗时,因为需要考虑细微差别以及通常措辞不佳的标题。


它由什么组成?

本问答包含的条目旨在提供有关如何:

  • 解析错误信息结构
  • 了解错误的含义
  • 持续复制(如适用)
  • 解决问题
  • 提供指向规范问答的链接(如果可能)

目录

为了帮助您浏览不断增长的参考资料,请使用以下目录:

  1. General errors
  2. Service-specific errors

这不是什么?

问答的范围仅限于常见(并非微不足道)。这不是:

  • 包罗万象的指南或“最佳做法”合集
  • 一般 ECMAScript 错误的参考
  • GAS 文档
  • 资源列表(我们有一个tag wiki

要添加什么?

添加条目时,请考虑以下几点:

  • 该错误是否足够普遍(有关示例,请参见“为什么”部分)?
  • 解决方案能否简明扼要并适用于大多数情况?

【问题讨论】:

标签: google-apps-script debugging google-apps-script google-api google-apps-script-addon


【解决方案1】:

前言

答案提供了关于在使用任何 Google 服务(内置和高级)或 API 时可能遇到的一般错误的指南。有关特定服务的错误,请参阅the other answer

Back to reference


一般错误


留言

TypeError:无法从undefined (or null) 读取属性“property name here

说明

错误消息表明您正在尝试访问 Object 实例上的属性,但在运行时期间,变量持有的值实际上是特殊数据输入undefined。通常,在访问对象的嵌套属性时会发生错误。

使用数值代替属性名称的此错误的变体表示应有 Array 的实例。由于 JavaScript 中的数组是 objects,所以这里提到的所有关于它们的内容都是正确的。

动态构造对象有一种特殊情况,例如 event objects,它们仅在特定上下文中可用,例如向应用发出 HTTP 请求或通过时间或基于事件的触发器调用函数.

错误是TypeError,因为应该是"object",但收到了"undefined"

如何解决

  1. 使用默认值
    JavaScript 中的Logical OR || 运算符有一个有趣的属性,即如果左侧是falsy,则计算右侧。由于 JS 中的对象是真实的,而 undefinednull 是虚假的,所以像 (myVar || {}).myProp [(myVar || [])[index] for arrays] 这样的表达式将保证不会抛出错误,并且属性至少是 undefined

    也可以提供默认值:(myVar || { myProp : 2 })保证访问myProp默认返回2。数组也是如此:(myVar || [1,2,3])

  2. 检查类型
    对于特殊情况尤其如此,typeof operator 结合 if statementcomparison operator 将允许函数在其指定上下文之外运行(即用于调试目的)或引入分支逻辑,具体取决于关于对象是否存在。

    可以控制检查的严格程度:

  • 松懈(“未定义”):if(typeof myVar !== "undefined") { //do something; }
  • 严格(“仅限正确的对象”):if(typeof myVar === "object" && myVar) { //do stuff }

相关问答

  1. GAS 项目的Parsing order 是问题的根源

留言

无法将some value 转换为data type

说明

由于传递的参数与方法预期的不同类型而引发错误。导致错误的常见错误是number to string 的意外强制。

如何重现

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

如何解决

确保错误消息中引用的值是文档要求的数据类型,并根据需要确保convert


留言

无法从此上下文调用Service and method name

说明

此错误发生在 上下文不匹配 上,并且特定于 container-bound 脚本。 导致错误的主要用例是尝试从另一个(即 @987654401 @来自电子表格)。

次要但也是突出的情况是调用服务not explicitly allowed 以从自定义函数(通常是由特殊JSDoc 样式注释@customfunction 标记并使用的函数)的结果作为公式)。

如何重现

对于绑定脚本上下文不匹配,在绑定到 Google 表格(或除 Google 文档之外的任何内容)的脚本项目中声明并运行此函数:

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

请注意,调用DocumentApp.getActiveDocument() 只会导致null 不匹配,并且执行会成功

对于自定义函数,使用下面在任何单元格中声明的函数作为公式:

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

如何解决

  1. 通过更改调用该方法的服务可以轻松解决上下文不匹配问题。
  2. 自定义函数无法调用这些服务,请使用custom menus or dialogs

留言

找不到方法Method name here

参数param namesmethod name 的方法签名不匹配

说明

这个错误对于新手来说是出了名的令人困惑的信息。它说的是在调用相关方法时传递的一个或多个参数中发生了类型不匹配

signature 没有与您的调用方式相对应的方法,因此“未找到”

如何解决

这里唯一的解决方法是仔细阅读文档并检查参数的顺序和inferred type 是否正确(使用具有自动完成功能的良好 IDE 会有所帮助)。但是,有时会发生问题,因为人们期望值是某种类型,而在 runtime 它是另一种类型。有几个技巧可以防止此类问题:

  1. 设置类型保护(typeof myVar === "string" 和类似的)。
  2. 由于 JavaScript 为 dynamically typed,因此添加了一个验证器来动态修复类型。

示例

/**
 * @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 或服务之一缺乏来自用户的足够权限。在其文档中包含 authorization 部分的每个服务方法都需要至少一个要授权的范围。

由于 GAS 本质上为方便开发而封装了 Google API,因此 OAuth 2.0 中列出的 API 范围 reference 中的大部分范围都可以使用,但如果在相应的文档中列出了其中的一个,那么使用它可能会更好有一些不一致的地方。

请注意,自定义函数在未经授权的情况下运行。从 Google 表格单元格调用函数是导致此错误的最常见原因。

如何解决

如果从脚本编辑器运行调用服务的函数,系统会自动提示您使用相关范围对其进行授权。尽管对于快速手动测试很有用,但最好在应用程序清单 (appscript.json) 中使用 set scopes explicitly。此外,如果打算发布应用程序,自动范围通常太宽而无法通过review

清单文件中的字段oauthScopes(如果在代码编辑器中则为View -> Show manifest file)应如下所示:

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

对于自定义函数,您可以通过切换到从菜单或按钮as custom functions cannot be authorized调用函数来修复它。

对于开发editor Add-ons 的开发人员,此错误意味着未处理的authorization lifecycle 模式:如果身份验证模式为AuthMode.NONE,则必须在调用需要授权的服务之前中止。

相关原因及解决办法

  1. @OnlyCurrentDoclimiting script access范围
  2. 范围autodetection

留言

ReferenceError: service name 未定义

说明

最常见的原因是使用 advanced service 而不启用它。启用此类服务​​后,指定标识符下的变量将附加到global scope,开发人员可以直接引用该变量。因此,当引用禁用的服务时,会抛出 ReferenceError

如何解决

转到“资源 -> 高级 Google 服务”菜单并启用引用的服务。请注意,标识符应等于引用的全局变量。 更详细的解释请阅读official guide

如果没有引用任何高级服务,则错误指向一个未声明的变量被引用。


留言

脚本完成但没有返回任何内容。

找不到脚本函数:doGet or doPost

说明

这本身不是错误(因为返回的 HTTP 响应代码是 200 并且执行被标记为成功,但通常被认为是成功。尝试从浏览器发出请求/访问时出现该消息 a脚本deployed as a Web App.

发生这种情况的主要原因有两个:

  1. 没有doGetdoPost触发函数
  2. 上面的触发器不返回HtmlOutputTextOutput 实例

如何解决

对于第一个原因,只需提供 doGetdoPost 触发器(或两者)函数。其次,确保您的应用程序的所有路由都以 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);
}

请注意,应该只声明一个触发函数 - 将它们视为应用程序的入口点。

如果触发器依赖parameter / parameters 来路由响应,请确保请求 URL 的结构为“baseURL/exec?query”或“baseURL/dev?query " 其中query 包含parameters to pass

相关问答

  1. Redeploying 声明触发器后

留言

很抱歉,发生服务器错误。请稍等,然后重试。

说明

这是最神秘的错误,几乎可以在任何服务的任何时候发生(尽管DriveApp 的使用特别容易受到它的影响)。该错误通常表明 Google 方面的问题在几个小时/几天内消失或在此过程中得到修复。

如何解决

没有灵丹妙药,通常,除了问题跟踪器上的filing an issuecontacting support(如果您有 GSuite 帐户)之外,您无能为力。在这样做之前,可以尝试以下常见的补救措施:

  1. 对于 bound 脚本 - 创建新文档并复制现有项目和数据。
  2. 切换到使用高级Drive service(始终记得先启用它)。
  3. 如果错误指向带有 1 的行,则可能会有 problem with a regular expression

不要对这个错误大发雷霆 - 尝试找到受影响的代码、文件或为问题加注星标,然后继续


没有明显问题的语法错误

此错误可能是由于在使用已弃用的 V8 运行时(在编写 GAS 平台时uses V8 时)使用 ES6 语法(例如箭头函数)引起的。

如何解决

打开“appscript.json”清单文件并检查runtimeVersion是否设置为"V8",如果不是则更改,否则删除任何ES6 features


配额相关错误

有几个与对服务使用的配额有关的错误。 Google 有其中的 comprehensive list,但根据一般经验法则,如果邮件与 “too many” 模式匹配,则您可能已超出相应的配额。

最可能遇到的错误:

  • 服务调用次数过多:service name
  • 运行的脚本太多
  • 一天使用过多计算机时间的服务
  • 此脚本的触发器过多

如何解决

在大多数情况下,唯一的解决方法是等到配额刷新或切换到另一个帐户(除非脚本部署为具有“以我身份运行”权限的 Web 应用程序,在这种情况下 所有者的 配额将在所有用户之间共享)。

要引用当时的文档:

每日配额在 24 小时窗口结束时刷新;但是,此刷新的确切时间因用户而异。

请注意,MailApp 等某些服务具有getRemainingDailyQuota 等可以检查剩余配额的方法。

在超过最大触发器数量的情况下,可以通过getProjectTriggers()(或检查"My triggers"选项卡)检查安装了多少触发器并采取相应措施减少数量(例如,通过使用deleteTrigger(trigger)来获取去掉一些)。

相关规范问答

  1. How are daily 限制正在应用和刷新?
  2. “超过最大执行时间”problem
  3. Optimizing service calls 减少执行时间

参考文献

  1. 如何出错messages more meaningful
  2. 调试custom functions

【讨论】:

  • 我认为可以为每个错误添加相关问题,例如this 以供进一步研究。
  • 嗨@TheMaster - 同意,刚刚发布了一个初始版本来试水。如果您知道任何规范的答案/问题,请告诉我。与此同时,我还将寻找类似的问题和相关主题
  • @TheMaster - 不用担心,这个问答的原因实际上是厌倦了看到不同风格的相同问题 - 我已经将答案设为社区维基,所以请随意编辑感谢您对这项工作的支持。
【解决方案2】:

特定于服务的错误

答案涉及与built-in service 相关的错误。有关一般参考,请参阅the other answer。欢迎提交解决服务问题的条目listed in official reference

Back to reference


电子表格应用程序

范围内的行数必须至少为 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());

错误:“引用不存在”

does not return a value 的电子表格单元格中调用自定义函数时发生错误。文档确实只提到了一个“必须返回要显示的值”,但这里的问题是空数组不是有效的返回值(没有要显示的元素)。

如何重现

在任何 Google 表格电子表格单元格中调用以下自定义函数:

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

如何解决

不需要具体处理,只要确定length &gt; 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() 方法调用的参数并相应地更改它们。


例外:您不能在已有过滤器的工作表中创建过滤器。

说明

该消息表示您试图在 Sheet 中调用 Range 上的 createFilter 方法,该 Sheet 已经具有过滤器集(通过 UI 或脚本),因此违反了每个 @ 1 个过滤器的限制987654360@,引用文档:

一张表中最多可以有一个过滤器。

如何重现

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

如何解决

添加一个保护,首先检查过滤器是否存在。 getFilter 如果在 Range 实例上调用,则返回过滤器或 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 or array 上不存在的属性,因此请检查您的 accessors 是否返回实际值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多