debugging 如何解决Google Apps脚本开发中的常见错误

d6kp6zgx  于 2022-11-14  发布在  Go
关注(0)|答案(2)|浏览(185)

问答目前是meta讨论的主题,请参与。目前的计划是尽可能分成问答。问答的答案是社区维客,当状态解决后,问题应该成为一个问题。

前言

本问答力求成为Google Apps Script语言开发过程中遇到的常见错误的集合和参考目标,以期提高google-apps-script标签的长期可维护性。
在其他语言和通用标记中也有几个类似的成功做法(参见c++androidphpphp again),这一个也是如此。
"为什么它存在“
无论是新开发人员还是有经验的开发人员,在开发和生产过程中遇到的关于错误的含义和解决方案的问题都可以有效地减少到一个单一的答案。

链接到最相关的副本对志愿者来说是困难和耗时的,因为需要考虑细微差别和通常措辞糟糕的标题。
"它由什么组成"
本问答中的条目旨在提供有关如何执行以下操作的信息:

  • 解析错误消息结构
  • 了解错误的含义
  • 一致重现(如适用)
  • 解决问题
  • 提供规范问答链接(如果可能)
    目录

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

  1. General errors显示器
  2. Service-specific errors
    "这不是什么"
    问答的范围仅限于 common(非琐碎)。这不是:
  • 包罗万象的指南或“最佳实践”集
  • 常规ECMAScript错误的参考
  • GAS文件
  • 一个资源列表(我们有一个tag wiki
    要添加什么?

添加条目时,请考虑以下事项:

  • 错误是否足够常见(参见“为什么”部分的示例)?
  • 该解决方案能否简明扼要地描述并适用于大多数情况?
dwbf0jvd

dwbf0jvd1#

前言

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

一般错误

消息

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

说明

该错误消息指示您正在尝试访问Object示例上的属性,但在 * 运行时 * 期间,变量 * 实际 * 持有的值是特殊数据类型undefined。通常,在访问对象的 * 嵌套 * 属性时会发生该错误。
此错误的一个变体是用数值代替属性名称,这表示应该是Array的示例。由于JavaScript中的数组是objects,因此这里提到的所有内容都是正确的。
有一种特殊情况是 * 动态构造 * 对象,例如event objects,它仅在特定上下文中可用,例如向应用发出HTTP请求或通过基于时间或事件的触发器调用函数。
错误为TypeError,因为预期为"object",但收到的是"undefined"

如何修复

1.使用默认值
JavaScript中的Logical OR||运算符有一个有趣的属性,即当左边为falsy时,计算右边的值。由于JS中的对象是true,而undefinednull是false,因此像(myVar || {}).myProp [ (myVar || [])[index] for arrays]这样的表达式将保证不会抛出错误,并且该属性至少为undefined
也可以提供默认值:默认情况下,(myVar || { myProp : 2 })保证访问myProp返回2。数组也是如此:(myVar || [1,2,3]) .
1.正在检查类型
特别是在特殊情况下,typeofoperatorifstatementcomparison operator的组合将允许函数在其指定的上下文之外运行(即用于调试目的),或者根据对象是否存在引入分支逻辑。
可以控制检查的严格程度:

  • lax(“未定义”):x1米19英寸1x
  • strict(“仅限适当对象”):x1米20英寸
    相关问答

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脚本。导致此错误的主要用例是尝试从另一种文档类型(例如电子表格中的DocumentApp.getUi())调用仅在一种文档类型 *(通常为getUi(),因为它是shared)中可用的方法。
第二个也是突出的例子是调用未明确允许从 * 自定义函数 *(通常是由特殊JSDoc样式注解@customfunction标记并用作公式的函数)调用的服务。

如何复制

对于绑定脚本上下文不匹配的情况,请在绑定到Google Sheets(或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.通过更改调用该方法的服务,可以很容易地修复上下文不匹配。
1.* 不能 * 使用自定义函数调用这些服务,请使用custom menus or dialogs

消息

找不到方法Method name here
参数param namesmethod name的方法签名不匹配

说明

这个错误对新手来说有一个非常令人困惑的信息。它说的是当调用有问题的方法时,在一个或多个传递的参数中发生了类型不匹配
signature没有与您调用它的方式相对应的方法,因此“未找到”

如何修复

这里唯一的解决方法是仔细阅读文档,检查参数的顺序和推断的类型是否正确(使用带有自动完成功能的良好IDE会有所帮助)。但是,有时候,问题的发生是因为人们期望值是某种类型,而在 runtime 它是另一种类型。有几个技巧可以防止这样的问题:
1.设置类型防护装置(typeof myVar === "string"及类似)。
1.由于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部分的每个服务方法都要求至少授权一个作用域。

As GAS essentially wraps around Google APIs for development convenience, most of the scopes listed in OAuth 2.0 scopes for APIs reference can be used, although if one is listed in the corresponding docs it may be better to use it as there are some inconsistencies.
Note that custom functions run without authorization. Calling a function from a Google sheet cell is the most common cause of this error.

How to fix

If a function calling the service is ran from the script editor, you are automatically prompted to authorize it with relevant scopes. Albeit useful for quick manual tests, it is best practice to set scopes explicitly in application manifest (appscript.json). Besides, automatic scopes are usually too broad to pass the review if one intends to publish the app.
The field oauthScopes in manifest file ( View -> Show manifest file if in code editor) should look something like this:

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

For custom functions, you can fix it by switching to calling the function from a menu or a button as custom functions cannot be authorized.
For those developing editor Add-ons, this error means an unhandled authorization lifecycle mode: one has to abort before calls to services that require authorization in case auth mode is AuthMode.NONE .

Related causes and solutions

  1. @OnlyCurrentDoclimiting script access scope
  2. Scopes autodetection

Message

ReferenceError: service name is not defined

Description

The most common cause is using an advanced service without enabling it. When such a service is enabled, a variable under the specified identifier is attached to global scope that the developer can reference directly. Thus, when a disabled service is referenced, a ReferenceError is thrown.

How to fix

Go to "Resources -> Advanced Google Services" menu and enable the service referenced. Note that the identifier should equal the global variable referenced. For a more detailed explanation, read the official guide.
If one hasn't referenced any advanced services then the error points to an undeclared variable being referenced.

Message

The script completed but did not return anything.
Script function not found: doGet or doPost

Description

This is not an error per se (as the HTTP response code returned is 200 and the execution is marked as successful, but is commonly regarded as one. The message appears when trying to make a request/access from browser a script deployed as a Web App .
There are two primary reasons why this would happen:

  1. There is no doGet or doPost trigger function
  2. Triggers above do not return an HtmlOutput or TextOutput instance

How to fix

For the first reason, simply provide a doGet or doPost trigger (or both) function. For the second, make sure that all routes of your app end with creation of TextOutput or HtmlOutput :

//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);
}

Note that there should be only one trigger function declared - treat them as entry points to your application.
If the trigger relies on parameter / parameters to route responses, make sure that the request URL is structured as " baseURL /exec? query " or " baseURL /dev? query " where query contains parameters to pass .

Related Q&As

  1. Redeploying after declaring triggers

Message

We're sorry, a server error occurred. Please wait a bit and try again.

Description

This one is the most cryptic error and can occur at any point with nearly any service (although DriveApp usage is particularly susceptible to it). The error usually indicates a problem on Google's side that either goes away in a couple of hours/days or gets fixed in the process.

How to fix

There is no silver bullet for that one and usually, there is nothing you can do apart from filing an issue on the issue tracker or contacting support if you have a GSuite account. Before doing that one can try the following common remedies:

  1. For bound scripts - creating a new document and copying over the existing project and data.
  2. Switch to using an advanced Driveservice (always remember to enable it first).
  3. There might be a problem with a regular expression if the error points to a line with one.
    Don't bash your head against this error - try locating affected code, file or star an issue and move on

Syntax error without apparent issues

This error is likely to be caused by using an ES6 syntax (for example, arrow functions) while using the deprecated Rhino runtime (at the time of writing the GAS platform uses V8).

How to fix

Open "appscript.json" manifest file and check if runtimeVersion is set to "V8" , change it if not, or remove any ES6 features otherwise.

Quota-related errors

There are several errors related to quotas imposed on service usage. Google has a comprehensive list of those, but as a general rule of thumb, if a message matches "too many" pattern, you are likely to have exceeded the respective quota.
Most likely errors encountered:

  • Service invoked too many times: service name
  • There are too many scripts running
  • Service using too much computer time for one day
  • This script has too many triggers
    How to fix

在大多数情况下,唯一的解决方法是等待配额刷新或切换到另一个帐户(除非脚本部署为具有“以我的身份运行”权限的Web App,在这种情况下,所有者的配额将由所有用户共享)。
要引用当时的文档:
每日配额在24小时窗口结束时刷新;然而,该刷新的确切时间在用户之间变化。
请注意,某些服务(如MailApp)具有类似getRemainingDailyQuota的方法,可以检查剩余配额。
如果超过触发器的最大数量,可以通过getProjectTriggers()(或检查"My triggers"选项卡)检查安装了多少触发器,并相应地减少数量(例如,通过使用deleteTrigger(trigger)删除一些)。

相关规范问答

  1. How are daily限制的应用和更新?
    1.“超过最大执行时间”problem
    1.优化服务调用以减少执行时间

参考文献

1.如何制作错误messages more meaningful
1.调试custom functions

2vuwiymt

2vuwiymt2#

特定于服务的错误

答案与built-in service相关的错误有关。有关一般参考,请参阅the other answer。欢迎提供解决listed in official 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());

错误:“引用不存在”
当在不返回值的电子表格单元格中调用自定义函数时,会发生该错误。文档确实只提到了一个“必须返回要显示的值”,但这里的陷阱是,空数组 * 也 * 不是有效的返回值(没有要显示的元素)。

如何复制

在任意Google工作表单元格中调用以下自定义函数:

/**
 * @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() * 不会 * 在要求不存在的范围时掷回)
    1.尝试在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一个过滤器的限制,引用文档:
一个工作表中最多只能有一个筛选器。

如何复制

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

如何修复

添加一个首先检查过滤器是否存在的保护。如果在Range示例上调用getFilter,则getFilter返回一个过滤器或null,它非常适合该作业:

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

URL提取应用程序

未提供任何值的属性:网址

说明

此错误特定于UrlFetchApp服务,并且在使用空字符串或非字符串值调用fetchfetchAll方法时发生。

如何复制

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

如何修复

请确保将包含URI(不一定有效)的字符串作为方法的第一个参数传递给该方法。由于其常见的根本原因是访问object or array上不存在的属性,请检查您的accessors是否返回实际值。

相关问题