通过


错误处理

仅当打开 公式级错误管理功能 时,本文中所述的行为才可用。

Power Fx 支持公式级错误处理。 对于所有新应用,此功能默认处于打开状态。 但是,某些较旧的应用可能在应用 设置中将其关闭。 使此功能保持打开状态。

  1. 在编辑模式下打开画布应用。
  2. 转到 “设置>更新>已停用 ”选项卡。
  3. 请确保禁用公式级管理功能处于关闭状态。

有关详细信息,请参阅 控制已启用哪些功能

错误发生。 网络出现故障,存储空间被填满,意想不到的值流入。 您的逻辑在面对潜在问题时能够继续正常工作这一点很重要。

默认情况下,错误流经应用的公式并报告给应用的最终用户。 这样,最终用户就知道发生了意外事件。 他们可能会使用其他输入自行解决问题,也可以向应用所有者报告问题。

作为应用创建者,控制应用中的错误:

  • 检测并处理错误。 如果可能出现错误,请编写应用的公式以检测错误条件并重试该作。 最终用户不需要担心发生错误,因为制作者考虑了这种可能性。 使用公式中的 IfErrorIsErrorIsErrorOrBlank 函数捕获错误。
  • 报告错误。 如果在遇到错误的公式中未处理错误,则错误将升至 App.OnError 处理程序。 无法替换错误,因为它已发生,并且是公式计算的一部分。 但是您可以使用 App.OnError 控制向最终用户报告错误的方式,包括一起隐藏错误报告。 App.OnError 还为整个应用的错误报告提供了一个通用阻塞点。
  • 创建并重新引发错误。 最后,可以使用自己的逻辑(特定于应用的条件)检测错误条件。 使用 Error 函数创建自定义错误。 在 IfErrorApp.OnError 中经过查询后,使用 Error 函数重新抛出错误。

入门指南

我们首先来看一个简单的示例。

  1. 在 Power Apps Canvas 应用中创建新屏幕。
  2. 插入 TextInput 控件。 默认为 TextInput1 名称。
  3. 插入一个标签控件。
  4. 将此标签控件的 Text 属性设置为公式
1/Value( TextInput1.Text )

包含“文本输入”的文本输入控件显示“无法将值转换为数字”的错误横幅的屏幕截图。

出现错误的原因是 TextInput 控件 "Text input"的默认文本无法转换为数字。 默认情况下,这是一件好事:最终用户会收到通知,指出应用中某些内容未按预期工作。

显然,您不希望用户每次启动应用时都遭遇错误提示。 总之,"Text input" 可能不是文本输入框的正确默认值。 若要解决此问题,请将 TextInput 控件的 Default 属性更改为:

Blank()

显示“除以零”的错误横幅的屏幕截图。

嗯,现在你看到的是另一个错误。 具有 空白的数学运算(例如除法)将空白值强制转换为零。 该值会导致除以零错误。 若要解决此问题,需要确定此应用中这种情况的适当行为。 答案可能是当文本输入为 时显示 空白。 可以通过使用 IfError 函数包装公式来实现此目标:

IfError( 1/Value( TextInput1.Text ), Blank() )

截图显示未显示错误横幅,因空值导致的错误已被替换为空白。

现在,错误将替换为有效值,错误横幅将消失。 但你可能过度使用了 IfError,它涵盖了所有错误,包括输入错误值,例如 "hello"。 可通过调整 IfError 仅处理除以零情况并重新抛出其他错误来解决此问题:

IfError( 1/Value( TextInput1.Text ), 
         If( FirstError.Kind = ErrorKind.Div0, Blank(), Error( FirstError ) ) )

截图显示未显示错误横幅,仅除以零错误被替换为空白,其余错误仍被重新抛出。

因此,运行应用并尝试一些不同的值。

如果没有任何输入值,就像应用启动时一样,不会显示任何答案,因为默认值为空白。但是,由于使用 IfError 替换了除以零错误,所以也没有错误信息显示。

显示未显示答案且没有错误横幅的屏幕截图。

如果键入 4,则会收到 0.25 的预期结果:

截图显示 0.25 值正常显示且无错误提示。

如果键入非法内容,例如 hello,则会收到错误横幅:

截图显示无法将 'hello' 转换为数字时,单元格空白且错误提示框出现。

这是一个简单的介绍性示例。 可以通过多种不同的方式处理错误,具体取决于应用的需求:

  1. 可以使用公式在标签控件中显示 “#Error” ,而不是错误横幅。 若要使替换类型与 IfError 的第一个参数兼容,需要使用 Text 函数显式将数值结果转换为文本字符串。
    IfError( Text( 1/Value( TextInput1.Text ) ), 
             If( FirstError.Kind = ErrorKind.Div0, Blank(), "#Error" )
    
    屏幕截图显示没有错误横幅,而是结果显示为 #Error。
  2. 可以编写集中式 App.OnError 处理程序,而不是用 IfError 包装此特定实例。 无法将显示的字符串替换为“#Error”,因为错误已发生,并且仅提供 App.OnError 来控制报告。
    If( FirstError.Kind <> ErrorKind.Div0, Error( FirstError ) )
    

错误传播

错误在公式中流动的方式与在 Excel 中的极为相似。 例如,在 Excel 中,如果单元格 A1 具有公式 =1/0,则 A1 将显示错误值 #DIV0!

Excel 电子表格的屏幕截图,A1=1/0,并且单元格中显示 #DIV/0!。

若单元格 A2 通过公式 =A1*2 引用 A1,错误将通过该公式传播:

截图显示 Excel 表格中 A2 单元格公式为 A1*2,单元格显示 #DIV/0! 错误。

错误替换了原本应由公式计算的值。 单元格中 A2 中的乘法没有结果,只有 A1 中的除法错误。

Power Fx 的工作方式相同。 通常,如果将错误作为函数或运算符的参数提供,则不会执行该作。 输入错误作为运算结果传递至后续单元格。 例如 Mid( Text( 1/0 ), 1, 1 ) 返回除以零错误,因最内层错误通过 Text 函数及 Mid 函数传递:

错误提示栏截图显示无效操作:除以零。

错误一般不会通过 Power Apps 控件的属性传播。 让我们使用另一个控件来扩展上一个示例,该控件显示第一个标签 Text 的属性是否为错误状态:

屏幕截图显示第二个标签控件上没有错误。

错误不会通过控件传播是合理的,因为系统会检测所有控件属性的输入错误。 错误并未丢失。

大多数函数和运算符都遵循“错误输入,错误输出”规则,但也有一些例外。 函数 IsErrorIsErrorOrBlankIfError 设计用于处理错误,因此即使有错误传递给它们,它们也可能不会返回错误。

观察错误

在公式使用错误值之前,Power Fx 不会检测到错误。

因此,即使输入错误,IfSelect 函数也可能不返回错误。 假定有 If( false, 1/0, 3 ) 这个公式。 该公式中存在除以零错误,但由于 false 条件导致 If 函数未执行该分支,Power Fx 和 Power Apps 未报告错误:

标签文本属性中使用 If 函数时未显示错误横幅的截图。

使用 Set 函数处理错误时,在将错误赋值给变量时不会报告错误。 例如在 Power Apps 中,App.OnStart 中的以下公式会将除零错误放入变量 x 中:

屏幕截图显示在 App.OnStart 中调用 Set 函数时未显示错误横幅。

不会报告任何错误,因为未引用 x。 但是,添加标签控件并将其 Text 属性 x设置为时,将显示错误:

截图显示标签控件引用变量 x 时显示的错误横幅。

可以使用 IfError、IsErrorIsErrorOrBlank 函数观察公式中的错误。 通过这些函数,您可在错误被察觉和报告前返回替代值、执行替代操作或修改错误内容。

报告错误

Power Fx 观察到错误后,下一步是向最终用户报告错误。

与 Excel 不同,公式结果未必总能显示在合适位置——例如控制项的 X/Y 坐标属性可能无法直接显示文本。 每个 Power Fx 主机都控制着错误最终如何呈现给终端用户,以及制作者对该过程的控制程度。 在Power Apps中,将显示错误横幅,App.OnError用于控制错误报告方式。

请务必注意,App.OnError 无法像 IfError 那样替换错误。 执行 App.OnError 时,错误已发生,结果通过其他公式传播。 App.OnError 仅控制如何向最终用户报告错误,并为制作者提供一个挂钩,以便在需要时记录错误。

范围变量 FirstErrorAllErrors 提供有关一个或多个错误的上下文信息。 此上下文提供有关错误类型、错误来源以及观察到错误的位置的信息。

出错后停止

行为公式支持采取行动、修改数据库和更改状态。 这些公式允许使用 ; 链接运算符(或 ;; 根据区域设置)在序列中执行多个操作。

例如,在此示例中,网格控件显示表中的内容 T 。 每个按钮选择都通过两个 Patch 调用来更改此表中的状态:

动画的屏幕截图,其中显示了在单击每个按钮后,表 T 中用随机数更新的两条记录。

在链式行为公式中,操作不会在遇到第一个错误后停止。 我们来修改示例,以在第一个 Patch 调用中传递一个无效的索引号。 尽管出现了之前的错误,但第二个 Patch 仍继续运行。 第一个错误将报告给最终用户,并在 Studio 的控件上显示为错误:

动画的屏幕截图,其中只显示表 T 中的第二条记录在每次单击后使用随机数进行更新,第一条记录会导致错误。

使用 IfError 在发生错误后停止执行。 与 If 函数类似,此函数的第三个参数提供了一个放置仅在没有错误时才应执行的操作的位置:

显示动画屏幕截图,其中表 T 中的任何记录都没有更改,因为 IfError 在发生错误后阻止了第二个操作的完成。

如果在 ForAll 的某个迭代过程中遇到错误,则其余迭代不会停止。 ForAll 旨在独立执行每次迭代,从而允许并行执行。 ForAll 完成后,将返回一个错误,其中包含遇到的所有错误(通过检查 IfErrorApp.OnError 中的 AllErrors)。

例如,以下公式导致 ForAll 返回两个错误(因 Value 为 0 导致两次除以零错误),而 Collection 包含三条记录(当 Value 不为 0 时):[1, 2, 3]

Clear( Collection ); 
ForAll( [1,0,2,0,3], If( 1/Value > 0, Collect( Collection, Value ) ) );

处理多个错误

由于行为公式可以执行多项操作,因此它也可能会遇到多个错误。

默认情况下,应用会向最终用户报告第一个错误。 在此示例中,这两个 Patch 调用都失败,但第二次调用由于零除法错误而失败。 用户只看到有关索引的第一个错误:

错误横幅中显示的第一个索引错误的屏幕截图,其中未报告第二个错误。

IfError 函数和 App.OnError 可以访问使用 AllErrors 作用域变量遇到的所有错误。 在这种情况下,可以将此变量设置为全局变量,并查看遇到的两个错误。 它们按遇到它们的相同顺序出现在表中:

错误捕获到全局变量 PatchErrors 的截图,可见两个错误均存在。

非行为公式也可以返回多个错误。 例如,对要更新的一批记录使用 Patch 函数会返回多个错误,每条失败的记录都有一个错误。

表中的错误

如前所述,可以在变量中存储错误。 还可以在数据结构(如表)中包含错误。 此方法非常重要,因为它可确保任何一条记录上的错误无法使整个表失效。

例如,请考虑Power Apps中的此数据表控件:

数据表截图显示“倒数”字段输入 0 时引发除以零错误。

AddColumns 中的计算遇到其中一个值的除法为零错误。 对于那条记录,Reciprocal 列有一个错误值(除以零),但其他记录没有问题,是好的。 IsError( Index( output, 2 ) ) 返回 false 并 IsError( Index( output, 2 ).Value ) 返回 true。

如果筛选表时发生错误,则整个记录是错误的。 该操作仍会返回结果中的记录,以便最终用户知道曾有记录存在,并且知道出现了问题。

举个例子来说。 此处,原始表没有错误,但只要 Value 等于 0,筛选操作就会产生错误:

数据表的屏幕截图,其中显示了筛选器条件无法处理的两条记录的错误。

值 -5 和 -3 被正确筛选掉。值 0 会导致处理筛选器时出错,因此不清楚该记录是否应包含在结果中。 为了最大程度地提高最终用户的透明度并帮助创造者进行调试,该操作包括使用错误记录代替原始记录。 在本例中,IsError( Index( output, 2 ) ) 返回 true。

数据源错误

数据源中 PatchCollectRemoveRemoveIfUpdateUpdateIfSubmitForm 等修改数据的函数以两种方式报错:

  • 其中每个函数都返回一个错误值作为作的结果。 可以使用 IsError 检测错误,并像往常一样使用 IfErrorApp.OnError 替换或禁止错误。
  • 作后, Errors 函数还会返回先前作的错误。 此行为可用于在窗体屏幕上显示错误消息,而无需在状态变量中捕获错误。

例如,此公式检查 Collect 中的错误,并显示自定义错误消息:

IfError( Collect( Names, { Name: "duplicate" } ),
         Notify( $"OOPS: { FirstError.Message }", NotificationType.Warning ) )

Errors 函数还返回有关运行时操作期间的过去错误的信息。 在不需要在状态变量中捕获错误的情况下,它对于在表单界面上显示错误很有用。

重新引发错误

有时,你期望潜在的错误,并可以安全地忽略它们。 在 IfErrorApp.OnError 内部,若检测到需传递给更高层处理器的错误,请使用 Error( AllErrors ) 重新抛出。

创建自己的错误

还可以使用 Error 函数创建自己的错误。

如果创建自己的错误,请使用大于 1,000 的值来避免与将来的系统错误值发生潜在冲突。

ErrorKind 枚举值

ErrorKind 枚举 价值 说明
分析错误 18 系统错误。 编译器分析存在问题。
错误的语言代码 14 使用了无效或无法识别的语言代码。
BadRegex 15 正则表达式无效。 检查与 IsMatchMatchMatchAll 函数配合使用的语法。
冲突 6 您正在更新的记录已在源端被修改,需解决冲突。 常见的解决方案是保存任何本地更改、刷新记录并重新应用更改。
ConstraintViolated 8 该记录未通过服务器上的约束检查。
创建权限 3 您无权为数据源创建记录。 例如,调用了 Collect 函数。
删除权限 5 您无权删除数据源的记录。 例如,调用了 Remove 函数。
Div0 13 除数为零。
编辑权限 4 您无权为数据源创建记录。 例如,调用了 Patch 函数。
生成值 9 对于服务器自动计算的字段,错误地将值传递给服务器。
无效功能使用 16 函数使用无效。 函数的一个或多个参数通常不正确或以其使用方式无效。
文件未找到 17 找不到 SaveData 存储区域。
内存不足 21 设备上没有足够的内存或存储空间来进行操作。
无效参数 25 向函数传递了无效参数。
内部 26 系统错误。 其中一个函数存在内部问题。
MissingRequired 2 记录缺少一个必填字段。
网络 23 网络通信存在问题。
None 0 系统错误。 不存在错误。
不适用 27 无可用值。 此错误有助于区分可以在数值计算中视为零的空白值,与应被标记为潜在问题的空白值(当使用该值时)之间的区别。
未找到 7 找不到记录。 例如,要在 Patch 函数中修改的记录。
不支持 20 此播放器或设备不支持的操作。
数字 24 数字函数的使用方式错误。 例如,Sqrt 的参数为 -1。
QuoteExceeded 22 已超出存储配额。
ReadOnlyValue 10 列是只读的,无法修改。
ReadPermission 19 您无权读取数据源的记录。
同步 1 数据源报告了错误。 查看“消息”列了解详细信息。
未知 12 存在未知类型的错误。
验证 11 记录未通过验证检查。