Данная статья является анонсом новой функциональности.
Не рекомендуется использовать содержание данной статьи для освоения новой функциональности.
Полное описание новой функциональности будет приведено в документации к соответствующей версии.
Полный список изменений в новой версии приводится в файле v8Update.htm.
Реализовано в версии 8.3.18.1128
Спустя некоторое время после появления в платформе 1С:Предприятие веб-клиента возникла необходимость в поддержке асинхронной модели работы, которая в то время появилась в браузерах. Эта поддержка была реализована с использованием типа ОписаниеОповещения (NotifyDescription) и новых на то время асинхронных процедур.
В версии 8.3.18 внесены изменения для того, чтобы упростить работу с асинхронностью. В основе лежит идея, что разработчику проще и понятнее видеть код, состоящий из набора последовательно выполняемых операций и содержащий минимальное количество деталей, не связанных напрямую с решаемой им задачей. И сделанные изменения направлены на то, чтобы сделать код, имеющий дело с асинхронностью максимально подобным обычному последовательному коду.
Наиболее близким аналогом того, что добавлено по части асинхронности в 1С:Предприятие, является тип Promise и конструкции async/await языка JavaScript, введенные в стандартах ECMAScript 6 и ECMASript 2017, соответственно.
Асинхронные методы остаются доступными только там, где они были доступны и раньше. Сама суть асинхронности, как она уже реализована в 1С:Предприятии, не меняется. Речь идет о новой форме работы с асинхронностью.
Далее будут рассмотрены составные части новой механики, а кроме того некоторые особенности и типовые приемы.
Можно сказать, что новая форма работы с асинхронностью включает три составные части:
Рассмотрим эти составляющие.
Данный тип является центральной частью, вокруг которой группируются остальные составляющие. Если коротко, то Обещание – это обертка для, возможно пока неизвестного, результата выполнения асинхронной функции.
У асинхронной функции, как и у любой другой функции, могут быть два варианта завершения:
В соответствии с этим объект типа Обещание может находиться в одном из трех состояний:
Практически любое Обещание начинает свое существование в состоянии Pending. Затем, в зависимости от того, как завершится асинхронная функция, Обещание может перейти в состояние Success или Failure. И, если Обещание перешло в состояние Success или Failure, то в этом состоянии оно и останется до конца жизни.
Обещание является возвращаемым значением у «новых» асинхронных функций. Как правило, асинхронная функция возвращает Обещание, еще до того, как её выполнение будет фактически завершено. То есть, это Обещание находится в состоянии Pending.
На настоящий момент Обещание ни имеет конструкторов, методов и свойств, доступных из встроенного языка. То есть, новый объект Обещание может появиться только в результате вызова асинхронной функции. А всё, что можно сделать с объектом типа Обещание – это использовать его в качестве аргумента оператора Ждать.
В остальном Обещание является вполне обычным типом. И со значением типа Обещание можно делать то же, что и с любым другим значением - присваивать переменным, передавать в качестве значения параметра процедурам/функциям и т. п.
Для того, чтобы можно было воспользоваться новым подходом к работе с асинхронностью, у практически всех существующих асинхронных процедур платформы в версии 8.3.18 появляются аналоги в виде асинхронных функций, возвращающих Обещание.
В дальнейшем ранее существовавшие асинхронные процедуры будем называть «старыми» асинхронными процедурами, а добавленные асинхронные функции, возвращающие Обещание, будем называть «новыми» асинхронными функциями.
Главное отличие «новых» асинхронных функции от «старых» асинхронных процедур состоит в способе, используемом для организации продолжения выполнения кода после фактического завершения асинхронной операции. В «старых» асинхронных процедурах для этого использовался параметр <ОписаниеОповещения>, в который вызывающий код должен поместить объект ОписаниеОповещения, содержащий описание процедур, которые должны быть вызваны при успешном завершении асинхронной операции или при возникновении ошибки.
«Новые» асинхронные функции платформы не имеют параметра <ОписаниеОповещения>, но вместо этого возвращают Обещание, являющееся оберткой для результата выполнения асинхронной функции. И то, как вызывающий код обойдется с этим возвращаемым значением, целиком относится на усмотрение вызывающего кода.
У «старых» асинхронных процедур зачастую имелись прототипы в виде синхронных процедур или функций, на основании которых они были созданы.
Рассмотрим на примере.
В платформе 1С:Предприятие 8 имеется синхронная функция КопироватьФайл (СоpyFile), описание которой выглядит следующим образом:
|
Параметры:
Данный метод доступен повсеместно. Но на клиенте использование синхронных методов может быть запрещено. Начиная с версии 8.3.6, в платформе появилась асинхронная процедура НачатьКопированиеФайла (BeginCopyingFile) со следующим описанием:
|
Параметры:
И, наконец, в версии 8.3.18 добавляется «новая» асинхронная функция КопироватьФайлАсинх / CopyFileAsync):
|
Параметры:
Возвращаемое значение:
После успешного завершения копирования файла Обещание будет содержать путь к скопированному файлу. В случае ошибки Обещание будет содержать исключение.
На приведенном примере заметно, что по ряду признаков «новая» асинхронная функция имеет больше сходства с синхронным аналогом, чем со «старой» асинхронной процедурой. В частности, у «новой» асинхронной функции тот же список параметров, что и у синхронной процедуры. Да и имя новой асинхронной процедуры образовано простым добавлением суффикса Асинх / Async к имени синхронной процедуры.
Со «старой» асинхронной процедурой «новую» асинхронную функцию роднит то, что она тоже выполняется асинхронно. А также то, что Обещание при успешном завершении получает то же значение, что и первый параметр процедуры, представляемой объектом ОписаниеОповещения.
У некоторых «старых» асинхронных процедур ОписаниеОповещения должно содержать описание процедуры с единственным параметром <ДополнительныеПараметры>. В этом случае у добавленной «новой» асинхронной функции при успешном завершении Обещание будет всегда содержать Неопределено.
И, наконец, вершина всей конструкции – это Асинх и Ждать.
Асинх / Async представляет собой модификатор, который может применяться к процедуре или функции, написанной на встроенном языке. Использование данного модификатора делает процедуру или функцию асинхронной. Важной особенностью является также то, что оператор Ждать может использоваться только внутри Асинх процедур/функций.
Аргументом оператора Ждать / Await является Обещание. Логически, оператор Ждать выполняет ожидание завершения асинхронной функции, стоящей за объектом Обещание. При нормальном завершении функции результатом оператора является значение, которое вернула асинхронная функция. Если же внутри функции было выброшено исключение, то это же исключение вылетает из оператора Ждать.
Таким образом, в результате выполнения
|
может получиться одно из двух:
Рассмотрим некоторые особенности Асинх процедур и функций.
Все параметры Асинх процедур и функций передаются только по значению. В обычных процедурах и функциях для передачи параметра по значению перед его именем должно присутствовать ключевое слово Знач. В Асинх процедурах и функциях передача по значению происходит по умолчанию и является единственно возможным вариантом. Наличие или отсутствие ключевого слова Знач не имеет значения.
Таким образом, заголовки функции
|
и
|
полностью эквивалентны.
Асинх функция всегда возвращает Обещание. При успешном завершении в Обещание будет завернуто значение, которое было аргументом оператора Возврат. Если при выполнении Асинх функции произошло исключение, то в Обещание будет завернуто это исключение.
Исключение, выброшенное и не перехваченное внутри Асинх функции, никак не может попасть в вызывающий код в результате вызова функции как такового.
|
Единственный способ узнать, как завершилась Асинх функция – это использовать возвращенное ею Обещание как аргумент оператора Ждать.
|
Асинх процедура – это в первую очередь процедура, а потом уже Асинх. Как процедура она не возвращает значения. А если при выполнении Асинх процедуры возникнет не перехваченное исключение, то это приведет к выдаче пользователю сообщения об ошибке. Само это исключение никак не может быть перехвачено и обработано вызвавшим процедуру кодом.
Эта особенность ставит Асинх процедуры в несколько особенное положение. Асинх процедуры могут оказаться вполне уместными в качестве обработчиков команд и т. п. Обработчики команд вызываются из платформы и практически никогда из встроенного языка. И то, что выдача сообщения о не перехваченном исключении выполняется без отдельных усилий со стороны разработчика, может оказаться весьма полезным качеством.
Но если есть потребность в обработке таких исключений вызывающим кодом, следует рассмотреть использование Асинх функции.
Теперь посмотрим, как использование новых возможностей работы с асинхронностью может выглядеть на практике. Представим, что нам надо разработать форму для копирования файлов из одного каталога на клиентском компьютере в другой. Для простоты полагаем, что копироваться должны только файлы, непосредственно находящиеся в исходном каталоге. Для хранения и редактирования путей к каталогам форма включает два реквизита типа Строка: ИсходныйКаталог и ЦелевойКаталог. Выполнение копирования вызывается командой КопироватьФайлы.
Для начала напишем модуль формы с использованием синхронных методов платформы, в расчете на то, что использование синхронных методов на клиенте не запрещено.
|
В приведенном фрагменте модуля процедура КопироватьФайлы() является обработчиком команды КопироватьФайлы. Само копирование файлов выполняется процедурой КопироватьФайлыСинх(). Для поиска копируемых файлов используется синхронная функция платформы НайтиФайлы(), а для копирования каждого из найденных файлов – процедура платформы КопироватьФайл(). В процедуре КопироватьФайлы() производится перехват и обработка исключений, которые могут вылететь из КопироватьФайлыСинх().
Простой и понятный код. Вот только часто использование синхронных методов на клиенте запрещено. Посмотрим, как может выглядеть код, написанный с использованием новой техники работы с асинхронностью.
|
Внешнее сходство между «синхронным» и «асинхронным» модулями трудно не заметить. Сосредоточимся на различиях. В асинхронном варианте фактическое копирование файлов выполняет
|
То, что в «синхронном» варианте было процедурой, стало Асинх функцией. Появление модификатора Асинх обусловлено тем, что внутри функции вызываются новые асинхронные функции платформы НайтиФайлыАсинх>() и КопироватьФайлАсинх(), а возвращаемые ими значения используются как аргументы операторов Ждать.
Превращение процедуры в функцию объясняется тем, что в процедуре КопироватьФайлы() необходимо перехватить и обработать исключения, которые могут возникнуть при выполнении КопироватьФайлыАсинх().
В данном случае конкретное значение, завернутое в Обещание, возвращаемое из функции КопироватьФайлыАсинх()при нормальном завершении, не имеет значения. Вызывающему коду надо только отличить нормальное завершение от не нормального.
Поэтому при нормальном завершении КопироватьФайлыАсинх() в Обещание во всех случаях окажется завернутым Неопределено, на что указывает отсутствие явного возврата значения в КопироватьФайлыАсинх(). А в процедуре КопироватьФайлы() результат вычисления выражения
|
никуда не присваивается. Но при этом сам этот оператор находится внутри блока Попытка…Исключение для того, чтобы перехватить исключение которое может быть выброшено внутри КопироватьФайлыАсинх().
В ходе выполнения
|
Обещание, которое вернулось из «новой» асинхронной функции платформы НайтиФайлыАсинх(), попадет на вход оператора Ждать. И, после завершения выполнения НайтиФайлыАсинх() в переменную Файлы будет помещен результат поиска файлов или же оператор Ждать выбросит исключение, если в ходе выполнения НайтиФайлыАсинх() произошла ошибка.
Результат вычисления
|
также никуда не присваивается. В данной точке кода надо просто дождаться завершения копирования файла и выбросить исключение, если при выполнении КопироватьФайлАсинх() произошла ошибка.
Попробуем немного усложнить задачу и потребуем, чтобы при успешном завершении выдавалось бы сообщение о числе скопированных файлов. Для этого функция КопироватьФайлыАсинх() должна вернуть процедуре КопироватьФайлы() число скопированных файлов, а сама процедура КопироватьФайлы() должна вывести сообщение для пользователя.
Усовершенствованный модуль формы может выглядеть следующим образом:
|
Что изменилось?
В функции КопироватьФайлыАсинх() появилась локальная переменная Счетчик для подсчета числа скопированных файлов. Значение этой переменной в явном виде возвращается из функции.
В процедуре КопироватьФайлы() результат оператора Ждать теперь присваивается локальной переменной Сч, значение которой используется при выводе сообщения пользователю.
И, наконец, мы можем упростить процедуру КопироватьФайлы(), если в этой процедуре не требуется специфическая обработка исключений и достаточно просто выдачи сообщения пользователю.
|
Выше мы рассмотрели, как может выглядеть «новый» асинхронный код. Теперь настало время разобраться, как это работает.
Основная магия скрыта в операторе Ждать. Как уже отмечалось, с логической точки зрения оператор Ждать ожидает завершения асинхронной функции, стоящей за объектом Обещание. И может показаться, что это ожидание означает блокировку потока выполнения до момента завершения вернувшей Обещание асинхронной функции. Но это не так. Вспомним, что мы имеем дело все с той же асинхронностью, что уже присутствует в платформе 1С: Предприятие. Только в другой упаковке.
Новое состоит в том, что в операторе Ждать выполнение Асинх процедуры/функции может приостанавливаться и возобновляться. Приостановка означает выход из процедуры/функции с возможностью последующего продолжения. Но элемент стека вызовов, соответствующий приостановленной процедуре/функции не очищается и не удаляется. И при возобновлении выполнение начинается с той же точки, где оно было приостановлено с тем же окружением, включая значения локальных переменных, что было до приостановки.
Таким образом, выполнение оператора Ждать выглядит следующим образом:
Можно попробовать провести параллель между «новой» и «старой» асинхронностью. Рассмотрим небольшой фрагмент, выполняющий копирования файла с применением старой техники работы с асинхронностью.
|
Глядя на этот пример, можно сказать, что приостановка выполнения происходит на время выполнения «старой» асинхронной процедуры НачатьКопированиеФайла(), а возобновление выполнения происходит при завершении ее выполнения и заключается в вызове процедуры ПослеКопирования().
Теперь тот же фрагмент, написанный по-новому.
|
В этом примере приостановка выполнения происходит в операторе Ждать. Возобновление выполнения при завершении КопироватьФайлАсинх() происходит в этом же операторе. То есть оператор Ждать позволяет организовать точку приостановки/возобновления выполнения практически в любом месте кода без создания отдельной процедуры, объекта ОписаниеОповещения() и т. п.
Покажем на примере, что происходит при выполнении «нового» асинхронного кода. В качестве примера возьмем уже известный модуль формы, копирующий файлы из одного каталога в другой. Но для большей наглядности совсем чуть-чуть перепишем его.
|
Комментариями отмечены строки, представляющие интерес с точки зрения объяснения происходящего. Каждое из чисел в круглых скобках представляет собой порядковый номер шага в последовательности выполнения кода.
Начало выполнения процедуры КопироватьФайлы(). Вызов функции КопироватьФайлыАсинх().
Начало выполнения функции КопироватьФайлыАсинх(). Вызов асинхронной функции платформы НайтиФайлыАсинх().
Приостановка в операторе Ждать до завершения Об1. Возврат в процедуру КопироватьФайлы().
Приостановка в операторе Ждать до завершения Об (КопироватьФайлыАсинх()).
Возобновление выполнения КопироватьФайлыАсинх() в свзязи с завершением Об1 (НайтиФайлыАсинх()). Переменной Файлы присваивается результат поиска. Или, если при выполнении НайтиФайлыАсинх() произошла ошибка, выбрасывается исключение.
Вызов асинхронной функции платформы КопироватьФайлАсинх().
Приостановка функции КопироватьФайлыАсинх() до завершения Об2 (КопироватьФайлАсинх()).
Возобновление выполнения КопироватьФайлыАсинх() в взязи с завершением Об2 (КопироватьФайлАсинх()). Если при выполнении КопироватьФайлАсинх() произошла ошибка, выбрасывается исключение.
Окончательный выход из КопироватьФайлыАсинх(). Обещание, возвращенное функцией КопироватьФайлыАсинх() на шаге (3) переходит в состояние Success и становится контейнером для значения, содержащегося в переменной Счетчик.
Возобновление выполнения процедуры КопироватьФайлы() в связи с завершением Об (КопироватьФайлыАсинх()). Переменной Сч присваивается число скопированных файлов. Или, если при выполнении КопироватьФайлыАсинх() было выброшено не перехваченное исключение, выбрасывается это же самое исключение.
Окончательный выход из процедуры КопироватьФайлы().
Понятно, что шаги (6), (7) и (8) могут быть выполнены несколько раз, в зависимости от числа копируемых файлов.
В данной статье была предпринята попытка дать краткий, но всеобъемлющий обзор новой механики работы с асинхронностью. Краткий он, в том смысле, что занимает не слишком много места. Но, в то же время хочется верить, что достаточно внимания уделено всем необходимым для понимания деталям.