Обработка ошибок в блоках CATCH
Оператор CATCH определяет блок обработки ошибок конкретного типа в определенном блоке.
CATCH – Введение
Структурная обработка ошибок позволяет Вам предоставить пользовательский код обработки ошибок для любого типа ошибок. В ABL для этого служит оператор CATCH. Оператор CATCH определяет конечный (end) блок кода, который исполняется только, если было установлено состояние ERROR в связанном блоке (связанный блок – блок, в котором содержится данный блок CATCH) и тип ошибки совпадает (или является подтипом) с указанным в операторе CATCH.
Программа 36. Использование блока CATCH
DO TRANSACTION ON ERROR UNDO, THROW: FIND FIRST Customer WHERE CustNum=1000. RUN CreditCheck.p(Customer.CustNum). /* CATCH associated with DO TRANSACTION */ CATCH eAppError AS Progress.Lang.AppError: MESSAGE "This customer is on Credit Hold.". END CATCH. END. /* CATCH associated with Procedure (Main) block */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE "Customer record does not exist.". END CATCH.
В примере (Программа 36) директива THROW указывает, что любые необработанные ошибки передаются процедуре, так как процедура является внешним блоком для блока DO TRANSACTION. Блок CATCH в блоке DO будет обрабатывать любые ошибки типа Progress.Lang.AppError, которые могут возникнуть в операторе RUN. Если это происходит, CATCH обработает такую ошибку, и передачи ее в процедуру не будет. Однако, при выполнении программы возникнет ошибка Progress.Lang.SysError в операторе FIND. Так как эта ошибка не обрабатывается в блоке DO, она передается для обработки в процедуру. В процедуре имеется соответствующий блок CATCH – он и обработает эту ошибку. Объекты ошибок (eAppError и eSysError) создаются AVM автоматически и после обработки удаляются тоже автоматически.
Если удалить блок CATCH из процедуры, ошибка все равно передается в нее для обработки. Так как теперь явного обработчика нет, будет выполнена обработка по умолчанию, то есть выдача стандартного системного сообщения.
Блок CATCH выполняется на каждой итерации связанного с ним блока, если возникает соответствующая ошибка. Блок может содержать несколько блоков CATCH, все они должны располагаться в конце блока.
В блоке можно иметь только один блок CATCH для каждого типа ошибки. Однако, блок CATCH конкретного типа ошибки обрабатывает также и подтипы данного типа ошибки. Так что может быть несколько блоков CATCH для подтипов одного типа. Если конкретная ошибка может быть обработана несколькими блокам CATCH в данном блоке, будет использован первый подходящий CATCH. По этой причине блоки CATCH должны быть упорядочены в коде от наиболее специфического обработчика к наиболее общему.
Например, если Вы имеете обработчики для объектов Progress.Lang.SysError и для объектов SoapFaultError, Вы должны поместить блок для SoapFaultError первым, так как Progress.Lang.SoapFaultError является подтипом Progress.Lang.SysError.
CATCH – Синтаксис
Синтаксис оператора CATCH:
CATCH error-variable AS [ CLASS ] error-class:
.
.
.
END [ CATCH ] .
Здесь error-variable – имя переменной, представляющей объект ошибки (происходящий от встроенного класса Progress.Lang.ProError). ABL не требует явного описания этой переменной до ее использования. AVM распознает новые переменные в блоке CATCH и неявно создает соответствующее описание. Каждый блок CATCH в связанном блоке должен иметь уникальное имя переменной ошибки, иначе возникнет ошибка компиляции. Это имя может быть использовано повторно в другом связанном блоке. В то же время, имеет смысл описывать переменные ошибок явно для улучшения читаемости кода.
[ CLASS ] error-class – обычно Progress.Lang.SysError для системных ошибок или Progress.Lang.AppError (или его пользовательский подкласс) для ошибок приложения. Ключевое слово CLASS не является обязательным, но полезно для улучшения читаемости кода.
Если блок CATCH определен в простом блоке DO, ABL выдает ошибку компиляции, так как простой блок DO не имеет свойств обработки ошибок. Блок DO должен иметь либо TRANSACTION, либо ON ERROR для того, чтобы можно было использовать CATCH.
Код блока CATCH исполняется, только если в связанном блоке возникает ошибка типа error-class (или его подтипа). Это справедливо также, если вызванная из связанного блока процедура возвращает или устанавливает ошибку типа error-class. При возникновении ошибки, если в связанном блоке имеется активная транзакция, до выполнения CATCH будет произведен откат транзакции. Если имеются переменные или временные таблицы со свойством UNDO, они тоже будут откачены (причем, даже если активной транзакции не было).
Пример (Программа 37) демонстрирует использование блока CATCH для обработки любых системных ошибок ABL.
Программа 37. Использование CATCH для обработки системных ошибок
DEFINE VARIABLE iCust AS INTEGER. ASSIGN iCust = 5000. FIND Customer WHERE CustNum = iCust. /* Will fail */ /* Won't execute because FIND fails */ MESSAGE "Customer found" VIEW-AS ALERT-BOX BUTTONS OK. /* The associated block for this CATCH block is the main block of the .p */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE "From CATCH block..." SKIP eSysError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. END CATCH.
Связанный блок может содержать несколько блоков CATCH, каждый из которых будет обрабатывать различный класс ошибок. Если данный тип ошибки может быть обработан несколькими операторами CATCH, будет использован первый из них (по порядку в коде). Выполняется всегда только один блок CATCH! Более специализированные блоки CATCH должны располагаться в коде впереди более общих (Программа 38).
Программа 38. Множественные блоки CATCH
FOR EACH Customer: /* Code body of the associated block */ /* This CATCH specifies the most specialized user-defined error class. It will catch only myAppError error objects or objects derived from myAppError. */ CATCH eMyAppError AS Acme.Error.myAppError: /*Handler code for Acme.Error.myAppError condition. */ END CATCH. /* This CATCH will handle Progress.Lang.AppError or any user-defined application error type, except for eMyAppError which is handled by the preceding CATCH block. */ CATCH eAppError AS Progress.Lang.AppError: /* Handler code for AppError condition. */ END CATCH. /* This CATCH will handle any error raised by an ABL statement. Since it inherits from the same object as AppError in the class hierarchy, this CATCH could come before or after the CATCH for AppError */ CATCH eSysError AS Progress.Lang.SysError: /* Handler code for SysError condition. */ END CATCH. /* This will catch any possible error raised in ABL. */ CATCH eError AS Progress.Lang.Error: /* Handler code for any error condition. */ END CATCH. END. /* Associated Block */
Компилятор выдаст предупреждающее сообщение, если в блоке имеется недостижимый блок CATCH (Программа 39 – CATCH eMyAppError никогда не будет выполнен).
Программа 39. Недостижимый блок CATCH
FOR EACH Customer: /* Code body of the associated block */ /* This will catch all application errors */ CATCH eAppError AS Progress.Lang.AppError: /* Handler code for AppError condition */ END CATCH. /* Never get here, because myAppError is a subtype of Progress.Lang.AppError */ CATCH eMyAppError AS Acme.Error.myAppError: /* Handler code for myAppError condition */ END CATCH. END. /* Associated Block */
Если в блоке возникает ошибка, а соответствующего обработчика CATCH не существует, обработка будет выполнена в соответствии с фразой ON ERROR связанного блока (явной или неявной).
Допускается иметь явную фразу ON ERROR в заголовке связанного блока и операторы CATCH в нем – Вы можете обрабатывать только определенные типы ошибок в CATCH, оставляя остальные для обработки в ON ERROR.
Блоки, поддерживающие CATCH
Блок CATCH могут входить в следующие блоки:
- Блок DO (с фразами TRANSACTION или ON ERROR)
- Блок FOR
- Блок REPEAT
- Блок CATCH
- Блок FINALLY
- Блок Procedure (.p файл)
- Внутренняя процедура
- Пользовательская функция
- Блок ON
- Конструктор класса
- Деструктор класса
- Пользовательский метод класса
- Функция доступа к свойству класса
Блоки CATCH размещаются в конце связанного блока. Если также используется блок FINALLY, блоки CATCH размещаются перед ним. Общий синтаксис следующий:
associated-block:
.
.
.
[ CATCH
.
.
.
END [ CATCH ] . ] …
[FINALLY
.
.
.
END [ FINALLY ] . ]
END. /* associated-block */
Область действия UNDO и блок CATCH
Блок CATCH является конечным (end) блоком связанного с ним блока. Блок CATCH исполняется, только если в связанном блоке возникает ошибка и ее тип совпадает с типом, указанным в операторе CATCH. В момент выполнения блока CATCH несколько шагов процесса обработки ошибки уже выполнены, и это влияет на данные, доступные блоку CATCH:
- Любая транзакция в связанном блоке уже будет откачена. Другими словами, изменения, выполненные в связанном блоке для полей базы данных, переменных и временных таблиц UNDO уже отменены.
- Записи, видимые в связанном блоке, уже освобождены (смотри раздел Области видимости записей.).
- Даже если активной транзакции не было, откатываемые переменные и временные таблицы, измененные в связанном блоке, будут откачены до значений, которые они имели перед началом выполнения связанного блока.
Следующий пример (Программа 40) демонстрирует эти правила:
Программа 40. UNDO и блок CATCH
/* Defines an undoable variable because the NO-UNDO option is not specified. */ DEFINE VARIABLE TargetCustNum AS INTEGER. /* The last valid value before the beginning of the DO block */ ASSIGN TargetCustNum = 1. DO ON ERROR UNDO, LEAVE: /* This value will be undone on ERROR! */ ASSIGN TargetCustNum = 15. /* Find a Customer */ FIND Customer WHERE Customer.CustNum = TargetCustNum. /* Change the database! A transaction is now active. */ ASSIGN Customer.Name = Customer.NAME + " And Much More". /* Confirm change to persistent field. */ MESSAGE "Customer Name changed to: " Customer.Name VIEW-AS ALERT-BOX BUTTONS OK. /* ERROR raised. Control passes to CATCH block. */ FIND Order OF Customer WHERE OrderNum = 1234. /* Statement will not execute. */ DISPLAY Customer.CustNum SKIP Customer.Name SKIP OrderNum SKIP OrderStatus VIEW-AS TEXT WITH FRAME b SIDE-LABELS. CATCH eSysError AS Progress.Lang.SysError: /* Confirm if Customer record is available in CATCH. */ IF AVAILABLE (Customer) THEN MESSAGE "Customer record is still available." VIEW-AS ALERT-BOX BUTTONS OK. ELSE DO: MESSAGE "No Customer record is currently available." VIEW-AS ALERT-BOX BUTTONS OK. /* Re-Find the Customer. Cannot rely on value of TargetCustNum! */ FIND Customer WHERE Customer.CustNum = 15. /* Confirm that change to database field was not committed аnd UNDO variable was rolled back. */ MESSAGE "TargetCustNum = " TargetCustNum SKIP "Customer name is now: " Customer.Name VIEW-AS ALERT-BOX BUTTONS OK. END. /* ELSE */ END CATCH. END. /* DO */
Видимость буфера в блоке CATCH
Пример в предыдущем разделе (Программа 40) показывает, что буфер записи, видимый в связанном блоке, недоступен в блоке CATCH. Буфер может быть доступен, если, согласно правилам видимости, он видим вне связанного блока. Однако, даже доступный в блоке CATCH буфер будет откачен, если он является частью транзакции в связанном блоке. Следующий пример (Программа 41) демонстрирует видимость буфера для общего случая цикла по связанным записям.
Программа 41. Видимость буферов и блок CATCH
DEFINE VARIABLE i AS INTEGER NO-UNDO. /* An outer loop for batch updates to Customer records */ FOR EACH Customer WHERE CustNum < 2: /* Customer buffer scoped to FOR EACH. Will be available to inner CATCH. */ ASSIGN Customer.Name = Customer.Name + "_changed". /* Update Order records for current Customer. */ DO i = 1 TO 5 ON ERROR UNDO, LEAVE: IF i = 1 then FIND FIRST Order OF Customer. ELSE FIND NEXT Order OF Customer. /* Order record scoped to the DO block, which is the associated. */ ASSIGN Order.PromiseDate = TODAY. /* Nonsense code raises unique index error. DO block undone. Control passes to CATCH */ ASSIGN Customer.CustNum = 2. CATCH eSysError AS Progress.Lang.SysError: /* MESSAGE statement executes to display available Customer data. Unavailable Order data raises two errors. Errors thrown to FOR EACH block. FOR EACH block undoes iteration and attempts NEXT iteration. Since there is not another iteration, FOR EACH completes. */ MESSAGE Customer.Name SKIP Order.OrderNum Order.PromiseDate VIEW-AS ALERT-BOX BUTTONS OK. END CATCH. END. /* DO */ END. /* FOR EACH */ FIND Customer WHERE Customer.CustNum = 1. /* Demonstrates change made in FOR EACH block is also undone. */ MESSAGE Customer.Name VIEW-AS ALERT-BOX BUTTONS OK.
Здесь внешний цикл идет по записям Customer, a внутренний – по записям Order, связанным с текущим Customer. Во внутреннем цикле происходит ошибка, и буфер Order не доступен блоку CATCH, связанному с внутренним циклом. Буфер Order видим во внутреннем блоке и после выполнения UNDO запись освобождена и недоступна. Буфер Customer видим во внешнем блоке, так что блок CATCH имеет к нему доступ и показывает незавершенное изменение записи Customer. Так как блок CATCH сам по себе вызывает ошибку, пытаясь показать недоступные данные записи Order, эти ошибки передаются блоку FOR, который откатывает изменения в записи Customer.
Передача управления из блока CATCH
Код в блоке CATCH может содержать явную директиву передачи управления, такую как LEAVE, NEXT, RETRY, RETURN, или THROW. Для RETRY или THROW требуется опция UNDO. Так как блок CATCH сам по себе откатываемый, LEAVE, NEXT, and RETRY без указания метки относятся к блоку CATCH, а не связанному блоку.
Если вы хотите, чтобы операции LEAVE, NEXT, или RETRY относились к связанному блоку, Вы должны использовать его метку.
Явное указание UNDO, THROW в блоке CATCH вызывает установку состояния ERROR в блоке, который является охватывающим для связанного с CATCH блока, а не в самом связанном блоке.
В примере (Программа 42) LEAVE в блоке CATCH относится к самому блоку CATCH.
Программа 42. Передача управления из блока CATCH – Пример 1
DEFINE VARIABLE iOrdNum AS INTEGER. DEFINE VARIABLE lSomeCondition AS LOGICAL. FOR EACH Customer: UPDATE iOrdNum. FIND Order WHERE Order.CustNum = Customer.CustNum AND Order.OrderNum = iOrdNum. /* Can fail and raise ERROR */ DISPLAY Order.OrderNum Order.ShipDate. /* don't get here if FIND fails */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE "Order " iOrdNum " does not exist for Customer ". /* This LEAVE applies to the CATCH. Execution will retry the same customer */ IF lSomeCondition THEN DO: UNDO, LEAVE. END. ... /* more statements in the CATCH that will execute if UNDO, LEAVE didn't execute */ END CATCH. END. /* FOR EACH Customer */
В следующем примере (Программа 43), процедура дает пользователю три попытки ввести правильный номер заказа:
Программа 43. Передача управления из блока CATCH – Пример 2
DEFINE VARIABLE iOrdNum as INTEGER. DEFINE VARIABLE iTries as INTEGER. ASSIGN iTries = 1. /* Associated block has a label */ blk1: FOR EACH Customer: /* User input statement means default error handling is UNDO, RETRY */ DISPLAY Customer.Name. UPDATE iOrdNum. /* Will fail and raise ERROR if user enters invalid Order number */ FIND Order WHERE Order.CustNum = Customer.CustNum AND Order.OrderNum = iOrdNum. ... CATCH eSysError AS Progress.Lang.SysError: MESSAGE "Order " iOrdNum " does not exist for this Customer.". /* Note that iTries is not reset on UNDO of the FOR EACH because it is not referenced in the FOR EACH block. */ IF iTries <= 3 THEN DO: ASSIGN iTries = iTries + 1. END. ELSE DO: MESSAGE "Too many tries for this Customer". ASSIGN iTries = 1. /* Leave the CATCH. Execution will resume with the NEXT iteration of the FOR EACH */ UNDO, NEXT blk1. END. END CATCH. END. /* blk1 FOR EACH */
Так как в этом примере используется оператор ввода данных, это означает, что по умолчанию в блоке FOR установлена опция RETRY. Блок CATCH отменяет это умолчание, только если пользователь трижды ввел ошибочный номер заказа для данного клиента (Customer). В этом случае оператор UNDO, NEXT blk1 не откатывает связанный блок (это уже выполнено до начала исполнения CATCH), выполняется NEXT, и блок FOR выполняет следующую итерацию.
Замечание: Когда Вы работаете с вложенными блоками, имеется взаимосвязь между областью действия LEAVE (NEXT) и областью действия UNDO. Вы не можете выполнить переход по LEAVE или NEXT за пределы действия UNDO, если еще есть что откатывать. Если откат уже выполнен, можно перейти к более внешнему контексту.
Если явного оператора передачи управления нет, после завершения блока выполняется опция перехода для связанного блока, по умолчанию это RETRY, если в блоке есть пользовательский ввод, и NEXT или LEAVE, если ввода нет, для итерационных и не итерационных блоков соответственно.
Обратите внимание, выполняется именно опция перехода по умолчанию, даже если блок имеет явную фразу ON ERROR и в ней указана другая опция перехода – если CATCH перехватил ошибку, фраза ON ERROR не выполняется (Программа 44).
Программа 44. Передача управления из блока CATCH – Пример 3
Loop: /* Explicit UNDO, LEAVE for a block */ /* Defaults are UNDO, NEXT */ FOR EACH Customer WHERE CustNum < 5 ON ERROR UNDO, LEAVE: MESSAGE CustNum Name VIEW-AS ALERT-BOX INFO BUTTONS OK. /* FIND will fail */ FIND FIRST Order WHERE Order.CustNum = Customer.CustNum AND Order.OrderNum = 1234. /* Error will be processed by CATCH… */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE "From CATCH block..." SKIP eSysError:GetMessage(1) VIEW-AS ALERT-BOX BUTTONS OK. /* And execution will continue with NEXT Customer! */ /* Like with explicit UNDO, NEXT Loop here */ END CATCH. END. MESSAGE 'leave' VIEW-AS ALERT-BOX INFO BUTTONS OK.
Еще один пример (Программа 45): если перехвачена ошибка Acme.Error.myAppError, явный оператор UNDO, THROW передаст ошибку блоку, охватывающему FOR EACH, но при перехвате Progress.Lang.SysError будет выполнен NEXT для блока FOR EACH.
Программа 45. Передача управления из блока CATCH – Пример 4
FOR EACH Customer: /* FOR EACH code */ CATCH eSysError AS Progress.Lang.SysError: /* Handler code for SysError condition */ /* RETRY on FOR EACH after leaving the CATCH, which becomes NEXT if there are no I/O statements.*/ END CATCH. CATCH myAppErr AS Acme.Error.myAppError: /* Handler code for myAppError condition */ UNDO, THROW myAppErr. /* THROW error to block enclosing FOR EACH */ END CATCH. END.
Блок CATCH внутри блока CATCH
Блок CATCH представляет собой откатываемый (undoable) блок с неявным свойством обработки ошибок ON ERROR UNDO, THROW. Вы не можете явно указать фразу ON ERROR для блока CATCH. Если оператор внутри блока CATCH вызывает ошибку, и нет вложенного блока CATCH, блок CATCH будет откачен и ошибка будет передана блоку, охватывающему связанный блок. То есть будет выполнено:
- Откат (UNDO) блока
- Выход из блока CATCH и его связанного блока.
- Передача (THROW) ошибки охватывающему блоку. Если блок CATCH находится на уровне процедуры, ошибка будет передана в вызывающую процедуру.
Это соответствует явному оператору UNDO, THROW в блоке CATCH.
Блок CATCH может содержать внутри себя другой блок CATCH. В этом случае внутренний блок CATCH обрабатывает только ошибки, возникшие во внешнем блоке CATCH. Для предотвращения зацикливания любой оператор UNDO, THROW во внешнем блоке CATCH или любом вложенном блоке CATCH немедленно передает ошибку блоку, охватывающему блок, связанный с внешним блоком CATCH.
Программа 46. Вложенные блоки CATCH
FOR EACH Customer: /* FOR EACH code body */ DO ON ERROR UNDO, LEAVE: /* DO code body */ CATCH eAppError AS Progress.Lang.AppError: /* CATCH code body */ CATCH eSysError AS Progress.Lang.SysError: UNDO, THROW eSysError. /* Will be handled by CATCH anyError on FOR EACH... */ END CATCH. END CATCH. END. /* DO */ CATCH anyError AS Progress.Lang.Error: /* Handler code for anyError condition */ END CATCH. END. /* FOR EACH */
В примере (Программа 46) отметим оператор UNDO, THROW во вложенном блоке CATCH в блоке DO. Внешний блок CATCH, связанный с блоком DO обрабатывает ошибки приложения. Вложенный блок CATCH обрабатывает любые системные ошибки во внешнем блоке CATCH. Если ошибка происходит, оператор UNDO, THROW во вложенном блоке немедленно передаст управление блоку, охватывающему связанный блок внешнего блока CATCH. В данном случае блок DO является связанным, а блок FOR EACH – его охватывающий. Блок CATCH anyError в блоке FOR EACH будет обрабатывать ошибку. Если в связанном блоке есть блок FINALLY, его код будет выполнен до установки ошибки в охватывающем блоке (FOR EACH в данном случае).
Подавление системных сообщений в CATCH
Наличие блока CATCH в блоке вызывает подавление выдачи системных сообщений об ошибках для всех операторов внутри блока, так же как опция NO-ERROR работает для индивидуальных операторов. Если имеется блок CATCH для Progress.Lang.SysError, сообщения добавляются в объект Progress.Lang.SysError. Если блока CATCH для Progress.Lang.SysError нет, и ошибка Progress.Lang.SysError не передается внешнему блоку (через ON ERROR UNDO, THROW в заголовке блока), системные сообщения выдаются, и выполняется фраза ON ERROR для блока.
В примере (Программа 47) CATCH обрабатывает ошибку и системное сообщение подавляется:
Программа 47. Подавление ошибки в CATCH
DO ON ERROR UNDO, LEAVE: FIND Customer 1000. /* raises ERROR and throws Progress.Lang.SysError Error message suppressed and execution goes to CATCH */ MESSAGE "After Find". /* won't get here */ CATCH eSysError AS Progress.Lang.SysError: MESSAGE "Inside CATCH block" VIEW-AS ALERT-BOX. /* Leave the CATCH, then the DO */ END CATCH. END.
Обработка ошибок встроенных методов ABL
В традиционной обработке ошибок ошибки, возникающие при вызове методов встроенных системных объектов, рассматриваются как предупреждения. Состояние ERROR не устанавливается, но Вы можете использовать опцию NO-ERROR для сохранения сообщений в хэндлере ERROR-STATUS.
Когда в блоке имеется любой блок CATCH, ошибки во встроенных методах ABL устанавливают состояние ERROR. Даже если нет блока CATCH для обработки системных ошибок ABL, ERROR все равно устанавливается и обрабатывается неявной или явной фразой ON ERROR связанного блока.
Для примера рассмотрим код (Программа 48):
Программа 48. Обработка ошибок во встроенных методах ABL
DO ON ERROR UNDO, THROW: DEFINE VARIABLE hSocket AS HANDLE NO-UNDO. CREATE SOCKET hSocket. hSocket:CONNECT ("-H localhost -S 3333") /* NO-ERROR */. CATCH eSysError AS Progress.Lang.SysError: DISPLAY "CATCH handles connection error on hSocket." SKIP "Messages..." SKIP eSysError:GetMessage(1) FORMAT "x(70)" SKIP eSysError:GetMessage(2) FORMAT "x(70)". END CATCH. END. /* DO */ IF ERROR-STATUS:NUM-MESSAGES > 0 THEN DISPLAY "ERROR-STATUS handle traps connection error on hSocket." SKIP "Message..." SKIP ERROR-STATUS:GET-MESSAGE(1) FORMAT "x(70)" SKIP ERROR-STATUS:GET-MESSAGE(2) FORMAT "x(70)".
Когда в методе CONNECT возникает ошибка, выполняется блок CATCH и показывает два сообщения. Хэндлер ERROR-STATUS не используется, так что второй оператор DISPLAY не выполняется.
Если раскомментировать опцию NO-ERROR в вызове метода CONNECT( ), то состояние ERROR не возникает, блок CATCH не выполняется. Сообщения сохраняются в ERROR-STATUS и выполняется второй оператор DISPLAY.