本篇参考:https://developer.salesforce.com/docs/component-library/documentation/en/lwc/data_error

https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_errors.htm

在salesforce lwc开发的时候,我们在进行正常的业务处理基础上,也需要考虑捕捉异常系,对异常的内容根据正确的业务进行跳转到不同页面或者展示不同的报错信息等处理。通过上面的连接我们可以看到salesforce status code有几种,常用的页面开发的报错信息可能三种: 400/404/500。对以下的报错进行打印解析。

status code : 400

1)使用 wire adapter的 getRecord 搜索不存在或者FLS没有访问权限的字段

{
"status": 400,
"headers": {},
"body": {
"message": "INVALID_FIELD: \nSystemModstamp, Owner.SystemModstamp, Test_No_Access_Field__c, Account.LastModifiedDate\n ^\nERROR at Row:1:Column:347\nNo such column 'Test_No_Access_Field__c' on entity 'Contact'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.",
"errorCode": "INVALID_FIELD",
"statusCode": 400
}
}

2)使用wire adapter的 updateRecord,必填字段或者 restrict等场景导致更新失败的报错信息

{
"status": 400,
"headers": {},
"body": {
"message": "An error occurred while trying to update the record. Please try again.",
"output": {
"fieldErrors": {
"Active__c": [
{
"errorCode": "INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST",
"field": "Active__c",
"duplicateRecordError": null,
"fieldLabel": "Active",
"message": "Active: bad value for restricted picklist field: xx",
"constituentField": null
}
]
},
"errors": []
},
"statusCode": 400,
"enhancedErrorType": "RecordError"
}
}

3. 使用wire adapter的update record时,validation rule / trigger 触发

{
"status": 400,
"headers": {},
"body": {
"message": "An error occurred while trying to update the record. Please try again.",
"output": {
"fieldErrors": {},
"errors": [
{
"errorCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION",
"field": null,
"duplicateRecordError": null,
"fieldLabel": null,
"message": "Annual Revenue must over 0",
"constituentField": null
}
]
},
"statusCode": 400,
"enhancedErrorType": "RecordError"
}
}
{
"status": 400,
"headers": {},
"body": {
"message": "An error occurred while trying to update the record. Please try again.",
"output": {
"fieldErrors": {
"AnnualRevenue": [
{
"errorCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION",
"field": "AnnualRevenue",
"duplicateRecordError": null,
"fieldLabel": "Annual Revenue",
"message": "Annual Revenue must over 0",
"constituentField": null
}
]
},
"errors": []
},
"statusCode": 400,
"enhancedErrorType": "RecordError"
}
}

status code: 404

请求不存在的资源

{
"status": 404,
"headers": {},
"body": {
"message": "The requested resource does not exist",
"errorCode": "NOT_FOUND",
"statusCode": 404
}
}

static code 500

1)apex方式 validation rule / trigger针对字段或者表添加报错信息

{
"status": 500,
"headers": {},
"body": {
"fieldErrors": {
"xxField__c": [
{
"message": "xx field validation",
"statusCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION"
}
]
},
"pageErrors": [
{
"message": "test page level message",
"statusCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION"
}
],
"index": null,
"duplicateResults": []
}
}

2)apex方式 null pointer,除零等程序报错

{
"status": 500,
"headers": {},
"body": {
"message": "Divide by 0",
"isUserDefinedException": false,
"exceptionType": "System.MathException",
"stackTrace": "Class.xxx.xxx: line xx, column 1"
}
}

对报错信息的结构进行简单了解以后,接下来考虑如何进行公用组件封装变成一个通用的组件。首先需要考虑的是,哪些是我们需要捕获的error信息,然后展示到画面上,哪些是应该跳转到ERROR共通画面的,比如如果调用后台产生了 null pointer等错误信息,毫无疑问应该跳转到一个公用的访问错误的页面。不同的项目设计不同的需求有不同的实现。篇中的内容实现如下:

trigger / validation rule / lookup filter等 DML错误认为是自定义异常,需要展示在画面,告诉用户这些消息,以便让他们知道更好的去操作数据。数据权限以及后台程序处理的报错跳转到共通页面,联系管理员通过debug log去排查。接下来考虑自定义的处理。自定义处理有两种方式,一种是无表单DML操作,展示toast信息。另一种是有表单,在头部或者字段处展示错误信息。根据这些简单信息进行强化。

一. 实装校验是否有Error的工具类

这里errorCheckUtils组件封装了以下的功能:

  • isSystemOrCustomError:校验当前的错误是属于系统异常还是属于自定义异常。这里的判断方式其实也比较暧昧。我们在这里声明的自定义的异常为 validation rule / trigger或者是restrict或者是有 lookup filter的类型的字段,其他类型的异常我们归为系统异常,将会跳转到自定义error页面;
  • getPageCustomErrorMessageList:获取页面级别的错误。这种通常有两种情况,一个是validation rule中的error location为page级别的,另外一种是trigger中具体的sObject的addError操作;
  • getFieldCustomErrorMessageList:获取字段级别的错误。返回类型为:[{'key1':'value1','keyn','valuen'}]. 其中 key为表字段的api 名字,value为具体的报错。这种通常有两种情况,一个是validation rule中的error location为field级别,另外一种是trigger中的具体的sObject的某个字段的addError操作。
  • getPageAndFieldCustomErrorMessageList:获取页面和字段级别总计的错误信息。

我们在看上面的链接可以看出来,errorItem的body可能返回出来一个数组,这里进行了简单的操作,直接获取了第一个操作。

const isSystemOrCustomError = (errorItem) => {
let errorBody;
let isSystemError = false;
if (Array.isArray(errorItem.body)) {
errorBody = errorItem.body[0];
} else {
errorBody = errorItem.body;
} if(errorBody.pageErrors || errorBody.fieldErrors || errorBody.output.errors || errorBody.output.fieldErrors) {
isSystemError = false;
} else {
isSystemError = true;
} return isSystemError;
} const getPageCustomErrorMessageList = (errorItem) => {
let pageErrorMessages = [];
let errorBody;
if (Array.isArray(errorItem.body)) {
errorBody = errorItem.body[0];
} else {
errorBody = errorItem.body;
} if(errorBody.pageErrors && Array.isArray(errorBody.pageErrors) && errorBody.pageErrors.length > 0) {
errorBody.pageErrors.forEach(field => {
pageErrorMessages.push(field.message);
});
} else if(errorBody.output && errorBody.output.errors && Array.isArray(errorBody.output.errors) && errorBody.output.errors.length > 0) {
errorBody.output.errors.forEach(field => {
pageErrorMessages.push(field.message);
});
} return pageErrorMessages;
} const getFieldCustomErrorMessageList = (errorItem) => {
let resultMessageList = [];
let errorBody;
if (Array.isArray(errorItem.body)) {
errorBody = errorItem.body[0];
} else {
errorBody = errorItem.body;
} let fieldErrors; if(errorBody.fieldErrors || errorBody.output.fieldErrors) {
if(errorBody.fieldErrors) {
fieldErrors = errorBody.fieldErrors;
} else {
fieldErrors = errorBody.output.fieldErrors;
} for(let key in fieldErrors) {
if (fieldErrors.hasOwnProperty(key)) { // Filtering the data in the loop
let fieldErrorMessages = fieldErrors[key];
let errorMessage;
if(Array.isArray(fieldErrorMessages) && fieldErrorMessages.length > 0) {
errorMessage = fieldErrorMessages[0].message;
} else {
errorMessage = fieldErrorMessages.message;
}
resultMessageList.push({"key" : key,"value" : errorMessage});
}
}
}
return resultMessageList;
} const getPageAndFieldCustomErrorMessageList = (errorItem) => {
let pageErrorMessages = [];
let errorBody;
if (Array.isArray(errorItem.body)) {
errorBody = errorItem.body[0];
} else {
errorBody = errorItem.body;
} if(errorBody.pageErrors && Array.isArray(errorBody.pageErrors) && errorBody.pageErrors.length > 0) {
errorBody.pageErrors.forEach(field => {
pageErrorMessages.push(field.message);
});
} if(errorBody.output && errorBody.output.errors && Array.isArray(errorBody.output.errors) && errorBody.output.errors.length > 0) {
errorBody.output.errors.forEach(field => {
pageErrorMessages.push(field.message);
});
} let fieldErrors; if(errorBody.fieldErrors || errorBody.output.fieldErrors) {
if(errorBody.fieldErrors) {
fieldErrors = errorBody.fieldErrors;
} else {
fieldErrors = errorBody.output.fieldErrors;
}
for(let key in fieldErrors) {
if (fieldErrors.hasOwnProperty(key)) { // Filtering the data in the loop
let fieldErrorMessages = fieldErrors[key];
let errorMessage;
if(Array.isArray(fieldErrorMessages) && fieldErrorMessages.length > 0) {
errorMessage = fieldErrorMessages[0].message;
} else {
errorMessage = fieldErrorMessages.message;
}
pageErrorMessages.push(errorMessage);
}
}
} return pageErrorMessages;
} export { isSystemOrCustomError, getPageCustomErrorMessageList, getFieldCustomErrorMessageList, getPageAndFieldCustomErrorMessageList};

二. 构筑系统错误的公共跳转页面

1. 这里我们封装了一个公共的error跳转的公用组件 navigationUtils,使用的是navigation,因为navigation没法直接跳转到lwc,只能先跳转到aura,所以实现为aura套壳子来进行实现。这里需要特别强调的一点,如果你的项目包含了community,需要为community进行一个定制,因为community不支持navigation 传递参数,所以以下的内容对community不适用。如何适应community这里不做展示。因为这里需要有跳转操作,所以需要 import NavigationMixin

  • navigationErrorPage:跳转到 commonErrorPageAura这个aura component,通常 maincomInstance为this;
  • navigationWhenErrorOccur:调用上面的方法。
import { NavigationMixin } from 'lightning/navigation';

const navigationErrorPage = (maincomInstance,errorMessage) => {
maincomInstance[NavigationMixin.Navigate]({
type: 'standard__component',
attributes: {
componentName : 'c__commonErrorPageAura',
},
state : {
c__errorMessage : errorMessage
}
});
} const navigationWhenErrorOccur = (maincomInstance, error) => {
let errorBody;
if (Array.isArray(error.body)) {
errorBody = error.body[0];
} else {
errorBody = error.body;
}
navigationErrorPage(maincomInstance, errorBody.message);
} export {navigationErrorPage,navigationWhenErrorOccur};

2. commonErrorPageAura实现

commonErrorPageAura.cmp:因为需要实现跳转,所以这里需要 implements="lightning:isUrlAddressable",将error信息传递给子commonErrorPage组件。

<aura:component implements="lightning:isUrlAddressable" access="global">
<aura:attribute name="errorMessage" type="String"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}" ></aura:handler>
<c:commonErrorPage errorMessage="{!v.errorMessage}"></c:commonErrorPage>
</aura:component>

commonErrorPageAuraController.js:通过pageReference获取到param信息然后设置给errorMessage变量

({
doInit : function(component, event, helper) {
var myPageRef = component.get("v.pageReference");
var errorMessage = myPageRef.state.c__errorMessage;
component.set('v.errorMessage',errorMessage);
}
})

3. commonErrorPage这个lwc component的实现

commonErrorPage.html

<template>
<lightning-card>
<div class='slds-grid slds-grid--vertical slds-align--absolute-center slds-container--large'>
<div class='slds-align-middle slds-m-bottom--xx-large slds-m-top--xx-large'>
ERROR picture set here
</div>
<h4 class='slds-text-align--center slds-text-heading--large slds-text-color--weak slds-m-bottom--small'>error information</h4>
<p class='slds-text-align--center slds-text-heading--medium slds-text-color--weak'>
{errorMessage}
</p>
</div>
</lightning-card> </template>

commonErrorPage.js

import { LightningElement, api } from 'lwc';
export default class CommonErrorPage extends LightningElement {
@api errorMessage;
}

三. 针对自定义异常的捕捉以及展示实现

这种展示实现不同项目有不同的要求,我们参考标准画面以及具体的业务大概可以分成两种展示形式: Toast展示具体错误信息 & form表单中展示page level在头部,error level在具体字段信息。篇幅原因这里只展示 form表单方式。我们假设有一个edit form表单,要进行了update操作,针对update操作展示不同类型的错误信息操作。

1. errorMessageModal实现:标准的UI错误信息展示如下图所示,我们扒了以下对应的css以及布局效果,实现这个errorMessageModal

这里有三个变量, isShowErrorDiv用来判断是否展示这个modal,isShowMessage用来判断是否展示errorList详细信息,errorMessageList用来展示具体的page level错误信息。

<template>
<template if:true={isShowErrorDiv}>
<div class="pageLevelErrors" tabindex="-1" >
<div class="desktop forcePageError" aria-live="assertive" data-aura-class="forcePageError">
<div class="genericNotification">
<span class="genericError uiOutputText" data-aura-class="uiOutputText">
Review the errors on this page.
</span>
</div>
<template if:true={isShowMessage}>
<ul class="errorsList">
<template for:each={errorMessageList} for:item="errorMessageItem">
<li key={errorMessageItem}>{errorMessageItem}</li>
</template>
</ul>
</template>
</div>
</div>
</template>
</template>

对应的js端展示

import { LightningElement,api,track } from 'lwc';
export default class ErrorMessageModal extends LightningElement { @api isShowErrorDiv = false;
@api errorMessageList = [];
@track isShowMessage = false; renderedCallback() {
if(this.errorMessageList && this.errorMessageList.length > 0) {
this.isShowMessage = true;
} else {
this.isShowMessage = false;
}
}
}

四. 做一个demo,将整体串起来。

accountEditSample.html:此html用于展示字段,点击保存进行save操作

<template>
<lightning-record-edit-form
record-id={recordId}
object-api-name="Account"
onsubmit={handleSubmit}
>
<c-error-message-modal is-show-error-div={isShowErrorDiv} error-message-list={errorMessageList}></c-error-message-modal>
<lightning-layout multiple-rows="true">
<lightning-layout-item size="6">
<lightning-input value={nameValue} label="name" name="accountName" class="accountName" onchange={handleInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-input value={annualRevenueValue} label="annual revenue" class="accountRevenue" name="accountRevenue" onchange={handleInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item size="12">
<div class="slds-m-top_medium">
<lightning-button class="slds-m-top_small" label="Cancel" onclick={handleReset}></lightning-button>
<lightning-button class="slds-m-top_small" type="submit" label="Save Record"></lightning-button>
</div>
</lightning-layout-item>
</lightning-layout>
</lightning-record-edit-form>
</template>

accountEditSample.js:用于加载数据,验证数据以及保存数据操作,篇中为了简单展示效果,对ID使用了hard code,有一些写法也不是优化的,仅供效果展示

import { LightningElement,track,api,wire } from 'lwc';
import { updateRecord,getRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';
import { navigationWhenErrorOccur } from 'c/navigationUtils';
import {isSystemOrCustomError,getPageCustomErrorMessageList,getFieldCustomErrorMessageList} from 'c/errorCheckUtils';
import ACCOUNT_ID_FIELD from '@salesforce/schema/Account.Id';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';
import ACCOUNT_ANNUALREVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
const fields = [
ACCOUNT_ID_FIELD,
ACCOUNT_NAME_FIELD,
ACCOUNT_ANNUALREVENUE_FIELD
];
export default class AccountEditSample extends NavigationMixin(LightningElement) { @api recordId = '0010I00002U8dBPQAZ';
@track isShowErrorDiv = false;
@track errorMessageList = []; @track nameValue;
@track annualRevenueValue; @wire(getRecord, { recordId: '$recordId', fields })
wiredAccount({ error, data }) {
if(error) {
navigationWhenErrorOccur(this,error);
} else if(data) {
if(data.fields) {
this.nameValue = data.fields.Name.value;
this.annualRevenueValue = data.fields.AnnualRevenue.value;
}
}
} handleInputChange(event) {
let eventSourceName = event.target.name;
if(eventSourceName === 'accountRevenue') {
this.annualRevenueValue = event.target.value;
} else if(eventSourceName === 'accountName') {
this.nameValue = event.target.value;
}
} handleSubmit(event) {
event.preventDefault();
const fields = {};
fields[ACCOUNT_ID_FIELD.fieldApiName] = this.recordId;
fields[ACCOUNT_NAME_FIELD.fieldApiName] = this.nameValue;
fields[ACCOUNT_ANNUALREVENUE_FIELD.fieldApiName] = this.annualRevenueValue;
const recordInput = { fields };
this.errorMessageList = [];
this.isShowErrorDiv = false;
updateRecord(recordInput)
.then(() => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Account updated',
variant: 'success'
})
);
}).catch(error => {
let systemOrCustomError = isSystemOrCustomError(error);
if(systemOrCustomError) {
navigationWhenErrorOccur(this,error);
} else {
this.isShowErrorDiv = true;
this.errorMessageList = getPageCustomErrorMessageList(error);
console.log(JSON.stringify(this.errorMessageList));
let errorList = getFieldCustomErrorMessageList(error);
if(errorList && errorList.length > 0) {
errorList.forEach(field => {
this.reportValidityForField(field.key,field.value);
});
}
}
});
} reportValidityForField(fieldName,errorMessage) {
if(fieldName === 'Name') {
this.template.querySelector('.accountName').setCustomValidity(errorMessage);
this.template.querySelector('.accountName').reportValidity();
} else if(fieldName === 'AnnualRevenue') {
this.template.querySelector('.accountRevenue').setCustomValidity(errorMessage);
this.template.querySelector('.accountRevenue').reportValidity();
}
} handleReset(event) {
const inputFields = this.template.querySelectorAll(
'lightning-input'
);
if (inputFields) {
inputFields.forEach(field => {
field.reset();
});
}
}
}

展示效果

1. 不包含权限等需要跳转到自定义error页面,我们把AnnualRevenue的FLS移除,则当前没有字段访问权限会报错

2. 触发validation或者trigger等效果

总结:篇中简单介绍了一下lwc中针对error的常用处理以及解析方式的简单实现。篇中有错误还请指出,有项目更优方案还请不吝赐教,有不懂欢迎留言。

Salesforce LWC学习(二十一) Error浅谈的更多相关文章

  1. Salesforce LWC学习(三十一) Quick Action适配

    本篇参考:https://www.lightningdesignsystem.com/components/modals/ 随着salesforce lwc的优化,越来越多的项目从aura转到了lwc ...

  2. Salesforce LWC学习(二十六) 简单知识总结篇三

    首先本篇感谢长源edward老哥的大力帮助. 背景:我们在前端开发的时候,经常会用到输入框,并且对这个输入框设置 required或者其他的验证,当不满足条件时使用自定义的UI或者使用标准的 inpu ...

  3. Salesforce LWC学习(二十三) Lightning Message Service 浅谈

    本篇参考: https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_specialist https://d ...

  4. Salesforce LWC学习(二十四) Array.sort 浅谈

    本篇参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort sal ...

  5. 智能车学习(二十一)——浅谈CCD交叉以及横线摆放

    一.CCD为何要交叉摆放?       首先使用横线摆放,CCD前瞻如果远一点,弯道丢线,再远一点直接窜道.所以需要很多很多代码的工作量,而且过弯的过程相当于没有任何的调节过程,就是一个偏差保持,或者 ...

  6. Salesforce LWC学习(二) helloWorld程序在VSCode中的实现

    上一篇我们简单的描述了一下Salesforce DX的配置以及CLI的简单功能使用,此篇主要简单描述一下LWC如何实现helloWorld以及LWC开发时应该注意的一些规范. 做国内项目的同学直观的感 ...

  7. Salesforce LWC学习(二十二) 简单知识总结篇二

    本篇参看: https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reactivity_fi ...

  8. Salesforce LWC学习(二十五) Jest Test

    本篇参看: https://trailhead.salesforce.com/content/learn/modules/test-lightning-web-components https://j ...

  9. Salesforce LWC学习(二十七) File Upload

    本篇参考: https://developer.salesforce.com/docs/component-library/bundle/lightning-file-upload/documenta ...

随机推荐

  1. P1359租用游艇(dp+dfs)

    好久真的是好久没有做dp的问题了(QWQ)(我有学过这玩意???) 诶,人生呐! 今天来一个动归- 顺便可以回顾一下dfs. 这个题我觉得审题也非常重要 小可爱dp: #include <bit ...

  2. Day01_虚拟化架构与系统部署

    学于千峰教育开源课程 感恩 千峰教育官网 b站在线视频 前言:本人所使用的操作系统是MacOS 使用的虚拟机软件为parallels desktop 本章结构 构建桌面端虚拟环境 虚拟机的概述 VMa ...

  3. ES6标准入门 2/26

    第一章 ECMAScript6 简介 1.首先经典开头,ECMAScript跟JavaScript的关系,前者是后者的规格,后者是前者的一种实现.在日常场合中,这两个词是可以互换的. 2.ES6可以泛 ...

  4. day9.关于文件的操作

    一.文件操作 """ fp = open("文件名",mode="模式",encoding="utf-8") ...

  5. 查看 Linux 系统服务的 5 大方法

    Linux 系统服务有时也称为守护程序,是在Linux启动时自动加载并在Linux退出时自动停止的系统任务. 在本文中,良许将为大家介绍如何列出 Linux 系统里所有运行的服务,以及如何检查某个服务 ...

  6. ACL2020 Contextual Embeddings When Are They Worth It 精读

    上下文嵌入(Bert词向量): 什么时候值得用? ACL 2018 预训练词向量 (上下文嵌入Bert,上下文无关嵌入Glove, 随机)详细分析文章 1 背景 图1 Bert 优点 效果显著 缺点 ...

  7. Android 布局的一些控件的补充和布局的补充(今儿没课)

    前面写的博客可能会有点乱: 1,是不太会排版. 2,就是我一边看书,一边听学长讲课,所以有的知识就融入进去了,我写的都是自己的意见和理解,大家取我精华,弃我糟粕哈. 今天是书上的内容,主要讲布局的,一 ...

  8. .NET和.NET Core Web APi FormData多文件上传对比

    前言 最近因维护.NET和.NET Core项目用到文件上传功能,虽说也做过,但是没做过什么对比,借此将二者利用Ajax通过FormData上传文件做一个总结,通过视图提交表单太简单,这里不做阐述,希 ...

  9. canvas小画板——(2)荧光笔效果

    我们在上一篇文章中讲了如何绘制平滑曲线 canvas小画板——(1)平滑曲线. 透明度实现荧光笔 现在我们需要加另外一种画笔效果,带透明度的荧光笔.那可能会觉得绘制画笔的时候加上透明度就可以了.我们来 ...

  10. java JDBC工具类

    package com.oracle.tools; import java.sql.Connection; import java.sql.DriverManager; import java.sql ...