本篇参考: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. 【前端】H5,底边按钮吸边,但是覆盖了列表循环的内容

    我的说情况大致类似于: PS:因为底边那个模块 绝对是浮动的,所有会遮住列表最下面一条现实的一部分, 解决:这个时候把body的底边的内边距调整到可显示的就可以了: body { background ...

  2. org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/book]] Tomcat ServletXml 异常

    此异常是因为xml配置serlvet-url-pattern缺少’/’     应该改为 /regist   背景: 写了base标签 form表单的action属性的值   个人分析: ️表单提交时 ...

  3. 新手程序员求职简历缺少这 3 点!别说8k薪资,4K你可能都拿不到!

    制作一份简历可能需要八百到千字,但HR看简历的时间只不过短短十秒,甚至可以说是一目十行. 我想针对想做程序员的刚毕业的学生分享着一点自己在求职招聘方面的感悟,不针对工作了多年的老同志了.快毕业那会儿, ...

  4. windows:shellcode 远程线程hook/注入(四)

    https://www.cnblogs.com/theseventhson/p/13236421.html  这里介绍了利用回调函数执行shellcode的基本原理:这里介绍另外一种利用回调执行she ...

  5. 记录一次jmeter脚本开发缺少utf-8惹的祸

    背景:需要模拟余额发放,并进行大批量的发放,我就想到了jmeter.就是几个简单的接口,我想很简单,就上手,没想到最后鸡鸡,害我查了半天原因. 操作:我编写的脚本: 请求默认值的内容编码我也是为空,当 ...

  6. 初识分布式:MIT 6.284系列(一)

    前言 本系列是源于「码农翻身」所属知识星球发起的读书活动,由大佬 @我的UDP不丢包 推荐而来,这次的读书活动有一些另类,我们抛弃了传统的书籍,开始攻略最高学府的研究生顶级课程 <6.824&g ...

  7. 用python包xlwt将数据写入Excel中

    一般用两种格式的数据写入,不多说放上demo. 1.列表形式写入 import xlwt def data_write(file_path, datas): f = xlwt.Workbook() s ...

  8. Apache Hudi + AWS S3 + Athena实战

    Apache Hudi在阿里巴巴集团.EMIS Health,LinkNovate,Tathastu.AI,腾讯,Uber内使用,并且由Amazon AWS EMR和Google云平台支持,最近Ama ...

  9. Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (default-testCompile) on project docker_springcloud_demo: Fatal error compiling: 无效的标记: -parameters -> [Help 1]

    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (def ...

  10. SpringBoot2.x下RabbitMQ的并发参数(concurrency和prefetch)

      RabbitMQ消费端配置 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest listene ...