本篇参考: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. 哇咔咔干货来啦:PowerJob 原理剖析之 Akka Toolkit

    本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列. Akka is a toolkit for bui ...

  2. luogu P6097 子集卷积 FST FWT

    LINK:子集卷积 学了1h多 终于看懂是怎么回事了(题解写的不太清楚 翻了好几篇博客才懂 一个需要用到的性质 二进制位为1个数是i的二进制数s 任意两个没有子集关系.挺显然. 而FST就是利用这个性 ...

  3. luogu P5892 [IOI2014]holiday 假期 决策单调性优化dp 主席树

    LINK:holiday 考虑第一个subtask. 容易想到n^2暴力枚举之后再暴力计算答案. 第二个subtask 暴力枚举终点可以利用主席树快速统计答案. 第三个subtask 暴力枚举两端利用 ...

  4. js数组常用api

    数组创建 第一种,使用 Array 构造函数: var arr1 = new Array(); //创建一个空数组 var arr2 = new Array(10); // 创建一个包含10项的数组 ...

  5. PHP开发者该知道的多进程消费队列

    引言 最近开发一个小功能,用到了队列mcq,启动一个进程消费队列数据,后边发现一个进程处理不过来了,又加了一个进程,过了段时间又处理不过来了… 这种方式每次都要修改crontab,如果进程挂掉了,不会 ...

  6. SSM三大框架的整合

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 在Java后端开发领域,Spri ...

  7. [转]Spring Security架构

    作者:before31原文:https://my.oschina.net/xuezi/blog/3126351 本指南是Spring Security的入门,它提供了对该框架的设计和基本构建的见解.我 ...

  8. 【小白学AI】线性回归与逻辑回归(似然参数估计)

    文章转自[机器学习炼丹术] 线性回归解决的是回归问题,逻辑回归相当于是线性回归的基础上,来解决分类问题. 1 公式 线性回归(Linear Regression)是什么相比不用多说了.格式是这个样子的 ...

  9. Java自学-JDBC 数据库连接池

    数据库连接池 与线程池类似的,数据库也有一个数据库连接池. 不过他们的实现思路是不一样的. 本章节讲解了自定义数据库连接池类:ConnectionPool,虽然不是很完善和健壮,但是足以帮助大家理解C ...

  10. rabbitMQ结合spring-boot使用(1)

    从这一节开始我们进入rabbitMQ的实战环节,项目环境是spring-boot 加maven.首先让我们创建一个spring-boot项目,然后引入web依赖和 rabbitMQ的依赖 <de ...