Work with asynchronous functions

1C Developer team

30.10.2020 16 min

Sometime after web client implementation in 1C:Enterprise platform, a need arose to support an asynchronous model of operation, which at the time became available in web browsers. This support was implemented using NotifyDescription type and asynchronous procedures that were innovative at that time.

Version 8.3.18 was modified to make asynchronous operations easier. Inherently, it is based on the idea that it is easier and more apprehensible for developers to see the code consisting of a sequence of operations and containing minimum details directly unrelated to the task being handled. Thus, the modifications made are intended to make the code dealing with asynchrony as close to the common sequential code as reasonably possible.

The Promise and async/await structures in JavaScript introduced in ECMAScript 6 and ECMAScript 2017 (accordingly) are the closest analogs to the asynchronous solutions now supported by 1C:Enterprise.

Asynchronous methods only remain available, where they were available previously. Essentially, asynchrony as it is implemented in 1C:Enterprise remains unchanged. The case in hand is the new form of working with asynchrony.

Further, components of new mechanics and certain distinctive features and standard approaches will be discussed.

Components

It is safe to say that a new form of working with asynchrony includes the following three principal components:

  • Promise type

  • The 'new' asynchronous platform functions that return Promise

  • Async and Await structures in 1C:Enterprise language

Let's have a look at these components in more detail.

Promise type

This type is the backbone the other components coalesce around. In simple terms, Promise is a wrapper for the asynchronous function execution result, which may be thus far unknown.

Similar to any other function, an asynchronous function may have two completion options, namely:

  • Normal completion

  • Exception

In accordance with the above, the Promise type object may have one of the following three statuses:

  • Pending means that synchronous function has not been completed yet and the result is not defined. This status is a starting point for Promise objects.

  • Success means that a function has been successfully completed. As a result, Promise becomes a container for a value returned by an asynchronous function.

  • Failure means that an exception was returned during the execution of an asynchronous function that was not caught in the function. And the Promise serves as a container for the above exception.

Almost every Promise starts in Pending status. Further, depending on the asynchronous function execution result, Promise may go to either Success, or Failure status. And if Promise changes for Success or Failure, it remains in the above status until the end of life.

Promise is a return value for the 'new' asynchronous functions. As a rule, an asynchronous function returns Promise well before it is actually completed. It means that Promise is Pending.

Currently, Promise has no constructors, methods, or properties that are available in 1C:Enterprise language. In other words, the new Promise object can appear only if an asynchronous function is called. And the only thing to do with Promise type object is using it as Await operator argument.

Apart from that, Promise is similar to a common type. Similarly, Promise type value can be handled in a way applicable to any other value, i.e., it can be assigned to a variable or passed as a parameter value to a procedure/function, etc.

'New' asynchronous platform functions

To use a new approach to asynchronous work, almost all existing asynchronous procedures in 8.3.18 have analogs, i.e., asynchronous functions that return Promise.

Further, we will call asynchronous procedures that existed before 'old' asynchronous procedures and those that have been just added and return Promise, 'new' asynchronous functions.

The main difference between 'new' and 'old' asynchronous functions is the method used to arrange further execution of the code upon actual completion of an asynchronous operation. As far as 'old' asynchronous procedures are concerned, <NotifyDescription> parameter was used for that purpose, where NotifyDescription object had to be placed by the calling code with a description of procedures to be called upon successful completion of an asynchronous operation or returning of an error.

'New' asynchronous platform functions do not have <NotifyDescription> parameter and instead, they return Promise acting as a wrapper for the asynchronous function execution result. And the way in which the calling code handles the return value is fully at its discretion.

'Old' asynchronous procedures often had prototypes in the form of synchronous procedures and functions that served as a basis for their generation.

Consider the following example. 1C:Enterprise has CopyFile synchronous function with is the description provided below.

CopyFile(<SourceFileName>, <ReceiverFileName>)

Parameters:

  • <SourceFileName> (required)
    Type: String
    Source file full name

  • <ReceiverFileName> (required)
    Type: String
    Source file full name

This method is common throughout the platform. However, using synchronous methods can be prohibited on the client. Since version 8.3.6, the platform supports BeginCopyingFile asynchronous procedure with the following description:

BeginCopyingFile(<NotifyDescription>, <SourceFileName>, <ReceiverFileName>)

Parameters:

  • <NotifyDescription> (required)
    Type: NotifyDescription
    It describes a procedure to be called upon completion with the following parameters:
  • <CopiedFile> - string with a path to a copied file
  • <AdditionalParameters> - value entered when NotifyDescription was created
  • <SourceFileName> (required)
    Type: String
    Source file full name
  • <ReceiverFileName> (required)
    Type: String
    Source file full name

Finally, a 'new' asynchronous function was added in version 8.3.18, that is CopyFileAsync:

CopyFileAsync(<SourceFileName>, <ReceiverFileName>)

Parameters:

  • <SourceFileName> (required)
    Type: String
    Source file full name
  • <ReceiverFileName> (required)
    Type: String
    Source file full name

Return value:

  • Type: Promise

As soon as a file is successfully copied, Promise will specify a path to the copied file. In case of an error, Promise will contain an exception.

As it follows from the example given above, certain features of a 'new' asynchronous function show that it is more similar to synchronous analog than an 'old' asynchronous procedure. Specifically, a 'new' asynchronous function and synchronous procedure have identical lists of parameters. Moreover, the 'new' asynchronous procedure name is generated by way of adding nothing else but Async suffix to that of the synchronous procedure.

The thing that an 'old' asynchronous procedure is executed asynchronously is typical of a 'new' asynchronous function as well. Further, upon successful completion Promise is assigned a value that is similar to that of the initial parameter in a procedure represented by NotifyDescription object.

As far as some 'old' asynchronous procedures are concerned, NotifyDescription must contain the procedure description with <AdditionalParameters> as the only parameter. In this scenario, an added 'new' asynchronous function will have Promise being always Undefined on successful completion.

Async and Await

Finally, the top of this structure is represented by Async and Await.

Async is a modifier applicable to a procedure or a function written in 1C:Enterprise language. Whenever this modifier is used, a procedure or a function becomes asynchronous. The key feature is that Await operator can only be used inside Async procedure or function.

Promise is an Await operator argument. Logically, Await waits for completion of an asynchronous function that follows Promise object. If a function is completed normally, the operator result is a value returned by an asynchronous function. If an exception is thrown in the function, the same exception is thrown by Await.

Therefore, upon execution of

Result = Await Prom; // Prom is of type Promise

one of the following two results can be obtained:

  • Result variable will be assigned a value that was wrapped in Promise as a result of normal completion of an asynchronous function.

  • Await throws an exception that was previously thrown in the asynchronous function and wrapped in Promise.

Let's consider certain features of Async procedures and functions.

Passing parameters by value

All parameters of Async procedures and functions are passed by value only. In a common procedure or function, the Val keyword has to be entered before the parameter name to pass it by value. In Async procedures and functions, passing by value is the default, and it is the only available option. It is no matter whether Val keyword is available or not.

Therefore, functions headers

Async Function CopyFileAsync(CatalogSource, CatalogDestanation)

and

Async Function CopyFileAsync(Val CatalogSource, Val CatalogDestanation)

are entirely equivalent.

Async always returns Promise

Async always returns Promise. Upon successful completion, a value will be wrapped in Promise, that was used as an argument for Return operator. If an exception is thrown during execution of Async function, then it is wrapped in Promise.

An exception that is thrown and is not caught inside Async cannot be available in the calling code as a result of a function call by itself.

&AtClient Procedure P() Try Funct1(); Except // Exception from Funct1() will not be caught here EndTry; EndProcedure &AtClient Async Function Funct1() Raise "Thrown into Funct1()"; EndFunction


The only way to know the result of Async execution is to use Promise returned by it as an argument for Await.

&AtClient Async Procedure P() Try Await NotNull(Null); Except // Exception from NotNull () will be caught here Message("Passed Null"); EndTry; EndProcedure &AtClient Async Function NotNull(P) If P = Null Then Raise "Thrown into Funct1()"; Else Return P; EndIf; EndFunction

Async procedure returns nothing

Async is firstly a procedure, and secondly, Async as a procedure, it returns no value. Whenever an exception that is not caught is thrown during the execution of Async, an error message is displayed to a user. The said exception itself cannot be caught and handled by the code that has called the procedure.

As such, Async procedures are made somewhat exceptional. Async can reasonably be used as command handlers, etc. Command handlers are called from the platform and almost never from 1C:Enterprise language. The fact that a message specifying that an exception has not been caught is displayed without any further action by the developer can prove to be rather useful.

However, if any such exception has to be handled by the calling code, then using Async function could be an option.

Example

Now, let's consider how new options for working with asynchrony can be used in practice. Imagine that we need to develop a form to copy files from one directory on a client PC to another one. For simplicity's sake, assume that files directly stored in the source directory are to be copied only. To store and edit paths to directories, the form has the following two String attributes: SourceDirectory and TargetDirectory. Copying is started by CopyFiles command.

To start with, let's write a form module using synchronous platform methods proceeding on the basis that use of synchronous methods is not prohibited on the client.

&AtClient Procedure CopyFiles(Command) Try CopyFilesSync(SourceDirectory, DestinationDirectory); Except Information = ErrorInfo(); DoMessageBoxAsync("An error has occurred: " + Information.Description); EndTry; EndProcedure &AtClient Procedure CopyFilesSync(SourceDirectory, DestinationDirectory) arrayFiles = FindFiles(SourceDirectory, "*", False); For Each File In arrayFiles Do SourceFile = SourceDirectory + "/" + File.Name; DestinationFile = DestinationDirectory + "/" + File.Name; CopyFile(SourceFile, DestinationFile); EndDo; EndProcedure

In the module snippet provided above, CopyFiles() procedure is a handler for CopyFiles command. Copying itself is performed by CopyFilesSync() procedure. To search for files to be copied, FindFiles() synchronous platform function is used, while CopyFile() platform procedure is used to copy each of the files found. CopyFiles() procedure catches and handles exceptions that may be thrown by CopyFilesSync().

This code is simple and easy to understand. However, frequently synchronous methods are not allowed to be used on the client. Let's look at the code written using a new method of working with asynchrony. 

&AtClient Async Procedure CopyFiles(Command) Try Await CopyFilesAsync(SourceDirectory, DestinationDirectory); //1 Except Information = ErrorInfo(); DoMessageBoxAsync("An error has occurred: " + Information.Description); EndTry; EndProcedure &AtClient Async Function CopyFilesAsync(SourceDirectory, DestinationDirectory) arrayFiles = Await FindFilesAsync(SourceDirectory, "*", False); //2 For Each File In arrayFiles Do SourceFile = SourceDirectory + "/" + File.Name; DestinationFile = DestinationDirectory + "/" + File.Name; Await CopyFileAsync(SourceFile, DestinationFile); //3 EndDo; EndFunction

Seemingly, the similarity of synchronous and asynchronous modules is obvious. Let's focus on dissimilarities. As far as asynchronous modules are concerned, the files are actually copied by

Async Function CopyFilesAsync(SourceDirectory, DestinationDirectory)

A procedure in the synchronous module became Async function. Async modifier has appeared, since new asynchronous platform functions (FindFilesAsync>() and CopyFileAsync()) are called inside the function, while return values serve as arguments for Await operators.

A procedure is transformed into a function since CopyFiles() procedure needs to catch and handle exceptions that can be thrown during CopyFilesAsync() execution.

In this particular scenario, a specific value wrapped in Promise and returned by CopyFilesAsync() does not matter if it is completed usually. The calling code only has to distinguish between normal and abnormal completion.

Therefore, upon typical completion of CopyFilesAsync(), Promise is always Undefined, thus showing that no value is explicitly returned by CopyFilesAsync(). Further, in CopyFiles() procedure, the result of the calculation of the following expression:

Await CopyFileAsync(SourceFile, DestinationFile); //1

is not assigned to anything. However, the operator itself is inside Попытка…Исключение block to catch another exception that may be thrown inside CopyFilesAsync().

During the execution of

arrayFiles = Await FindFilesAsync(SourceDirectory, "*", False); //2

Promise returned from a 'new' asynchronous platform function (FindFilesAsync()) goes to Await input. Finally, upon execution of FindFilesAsync(), arrayFiles variable has the result of file search or Await throws an exception, if an error is returned during the execution of FindFilesAsync().

The result of the calculation of

Await CopyFileAsync(SourceFile, DestinationFile); //3

is not assigned to anything, either. At this code point, we should just wait for files to copy and throw an exception if an error is returned during the execution of CopyFileAsync().

Let's raise the bar and require a message specifying the number of files copied to display upon successful completion. For that purpose, CopyFilesAsync() has to return the number of copied files to CopyFiles(), while CopyFiles() needs to display a message to the user.

The improved form module can be as follows:

&AtClient Async Procedure CopyFiles(Command) Try quantity = Await CopyFilesAsync(SourceDirectory, DestinationDirectory); //1 Message("Files copied: " + quantity); Except Information = ErrorInfo(); DoMessageBoxAsync("An error has occurred: " + Information.Description); EndTry; EndProcedure &AtClient Async Function CopyFilesAsync(SourceDirectory, DestinationDirectory) arrayFiles = Await FindFilesAsync(SourceDirectory, "*", False); //2 Counter = 0; For Each File In arrayFiles Do SourceFile = SourceDirectory + "/" + File.Name; DestinationFile = DestinationDirectory + "/" + File.Name; Await CopyFileAsync(SourceFile, DestinationFile); //3 Counter = Counter + 1; EndDo; Return Counter; EndFunction

What has changed?

In CopyFilesAsync(), a local variable (Counter) now exists to count the files copied. Its value is explicitly returned from the function.

In CopyFiles(), Await result is now assigned to Counter local variable, and the value of the latter is used to display a message to the user.

Finally, we are now able to simplify CopyFiles(), if no special handling of exceptions is required in the above procedure, and a message displayed to the user is more than enough.

&AtClient Async Procedure CopyFiles(Command) quantity = Await CopyFilesAsync(SourceDirectory, DestinationDirectory); //1 Message("Files copied: " + quantity); EndProcedure

How it works

Thus far, we discussed the expected appearance of a 'new' asynchronous code. Now, it is time to learn how it works.

The magic is concealed in Await operator. As mentioned above, logically, Await waits for completion of an asynchronous function that follows Promise object. It may seem that this awaiting means that execution flow is locked until an asynchronous function that returns Promise is completed. However, this is not the case. Recollect that we deal with asynchrony that already exists in 1C:Enterprise. The difference is packing only.

The new feature is that execution of Async procedure/function can be suspended and further resumed in Await operator. The suspension means exiting a procedure/function, which can subsequently be resumed. However, a call stack element that corresponds to a procedure/function so suspended is neither cleared nor removed. Upon resumption, it is executed exactly from the point where it has been suspended, with the same environment, including local variable values that existed before the suspension.

Therefore, Await is executed as follows:

·        The execution of a procedure/function is suspended.

·        Upon initial suspension, the control is returned to the code that originally called that procedure/function. Upon any further suspensions, it is returned to the system code that resumes its execution.

·        Execution is resumed as soon as Promise is completed. Further, Await returns a value or throws an exception that depends on what is wrapped in Promise.

We can try to draw a parallel between a 'new' and 'old' asynchrony. Let's consider a small snippet which copies a file using an old method of working with asynchrony.

&AtClient Procedure CopyFile(SourceFile, DestinationFile) Res = New NotifyDescription("AfterCopying", ThisObject); BeginCopyingFile(Res, SourceFile, DestinationFile); EndProcedure Procedure AfterCopying(File, AddParameter) Экспорт Message("File copied: " + File); EndProcedure

As it follows from the above example, execution is suspended for the term of execution of an 'old' asynchronous procedure, i.e. StartCopyFile(), and it is resumed as soon as it is completed by way of calling AfterCopy().

Now, see the same snippet written in a new way.

&AtClient

Async Function CopyFile(SourceFile, DestinationFile) Promise = CopyFileAsync(SourceFile, DestinationFile); Await Promise; Message("File copied: " + DestinationFile); EndFunction

It can be seen from the above example that execution is suspended in Await. Further, it is resumed in the same operator as soon as the execution of CopyFileAsync() is completed. In other words, Await enables you to set up suspension/resumption of execution almost at any point of the code without a need to create a separate procedure, NotifyDescription() object, etc.

This can be shown by an example, where a 'new' asynchronous code is executed. For illustrative purposes, let's take an already known form module that copies files from one directory to another. For clarity, we re-write it just a little bit.

&AtClient

Async Procedure CopyFiles(Command) Promise = CopyFilesAsync(SourceDirectory, DestinationDirectory); //(1) quantity = Await Promise; //(4) (10) Message("File copied: " + quantity); Return; //(11) EndProcedure &AtClient Async Function CopyFilesAsync(SourceDirectory, DestinationDirectory) Promise1 = FindFilesAsync(SourceDirectory, "*", False); //(2) arrayFiles = Await Promise1; //(3) (5) Counter = 0; For Each File In arrayFiles Do SourceFile = SourceDirectory + "/" + File.Name; DestinationFile = DestinationDirectory + "/" + File.Name; Promise2 = CopyFileAsync(SourceFile, DestinationFile); //(6) Await Promise2; //(7) (8) Counter = Counter + 1; EndDo; Return Counter; //(9) EndFunction

Comments mark the strings that are of interest for illustration purposes. Each value in parentheses is the number of a step in a code execution sequence.

  1. Start execution of CopyFiles(). Call CopyFilesAsync() function.

  2. Start execution of CopyFilesAsync(). Call FindFilesAsync() asynchronous platform function.

  3. Suspend in Await until completion of Promise1. Return to CopyFiles().

  4. Suspend in Await until completion of Promise1(CopyFilesAsync()).

  5. Resume execution of CopyFilesAsync() due to completion of Promise1 (FindFilesAsync()). Search result is assigned to arrayFiles variable. Alternatively, if during the execution of FindFilesAsync(), an error is returned, an exception is thrown.

  6. Call CopyFileAsync()asynchronous platform function.

  7. Suspend CopyFilesAsync() until Promise2 (CopyFileAsync()) is completed.

  8. Resume execution of CopyFilesAsync() due to completion of Promise2 (CopyFileAsync()). If during execution of CopyFileAsync() an error is returned, an exception is thrown.

  9. Final exit from CopyFilesAsync(). Promise returned by CopyFilesAsync() in step (3) goes to Success status and is used as a container for the value in Counter.

  10. Resume execution of CopyFiles() due to completion of Promise (CopyFilesAsync()). The number of copied files is assigned to Counter variable. Alternatively, if during the execution of CopyFilesAsync() an exception is thrown that is not caught, the same exception is thrown again.

  11. Finall exit from CopyFiles().

It is understood that steps (6), (7), and (8) can be repeated several times, depending on the number of files being copied.

Conclusion

This article attempts to give a brief and equally comprehensive summary of the new mechanics of working with asynchrony. It is brief to the effect that it is not so long. However, coincidently we would like to believe that all details that are relevant to this topic have been properly described and dealt with.

Be the first to know tips & tricks on business application development!

A confirmation e-mail has been sent to the e-mail address you provided .

Click the link in the e-mail to confirm and activate the subscription.