繁体   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