Работа некоторых блоков, как и загрузка ресурсов в Thunkable X, выполняется в асинхронном режиме, что вызывает немало вопросов у тех, кто считает, что блоки всегда выполняются последовательно друг за другом в порядке их расположения. Это приводит к появлению данных в случайном порядке и необходимости использования блока задержки при работе с ними, что является логической ошибкой.
Переменные используются не только для хранения значений данных, но и для выполнения различных операций над ними, например, сложение чисел, объединение строк, получение объекта из массива и др. Обо всём этом мы будем говорить в дальнейшем, а на этом занятии рассмотрим порядок выполнения блоков и асинхронный механизм работы.
Линейный порядок выполнения блоков
Это основной и самый простой порядок выполнения действий, когда блоки выполняются последовательно друг за другом. Рассмотрим простой пример вычисления суммы (”Sum string”).
Действия выполняются в следующем порядке:
Блоки выполняются в том порядке, в котором они расположены. Но это происходит не всегда. Механизм работы мобильных устройств основан на асинхронном механизме, при котором выполнение блоков не всегда происходит в порядке их следования.
Асинхронные блоки и асинхронный механизм работы
Главным недостатком синхронного механизма является то, что долгое выполнение какого-то одного блока приведёт к блокировке работы всех блоков, расположенных после него. Это называется блокировкой потока и похоже на ситуацию с трамваями: если один трамвай остановится (по причине поломки), то все остальные трамваи за ним также будут вынуждены остановиться из-за невозможности его объехать. Для устранения таких блокировок используется асинхронный механизм работы. Если в каком-то блоке произойдёт некритическая ошибка, то приложение сможет продолжить работу. Этот механизм чем-то напоминает карабельные переборки для улучшения непотопляемости судна.
Большинство блоков в Thunkable X являются синхронными. К ассинхронным блокам относятся фиолетовые блоки методов (функций компонентов) с секцией then do. Асинхронность данных блоков реализована за счёт использования функции обратного вызова (callback) - функции, которая вызывается после завершения работы другой функции. Функция обратного вызова исполняется не сразу, как обычные функции, а через некоторое время после завершения работы другой функции. Эта другая функция должна вызвать callback в случае cвоего успешного выполнения и в случае возникновения ошибки. Иначе разработчик не сможет запрограммировать действие при возникновении ошибки в методе.
Блоки функций также имеют фиолетовый цвет, но они работают синхронно.
Рассмотрим фиолетовый блок Realtime_DB1. Это асинхронный блок.
Блоки в секции then do выполняются не сразу, а только после выполнения метода Get, для чего требуется время, поскольку при вызове этого метода происходит обращение к облачной базе данных Firebase. Когда будет вызвана секция then do? Это зависит от скорости Интернет-соединения и получения данных из базы. Чем выше скорость канала связт и быстрее выполнение метода Get, тем раньше будет вызвана секция then do. Секция then do является телом функции обратного вызова.
Показанный блок в программировании можно записать так:
Realtime_DB1.Get( key, function( error, value ){
// then do
})
, где
Realtime_DB1 - это компонент
Get - метод компонента Realtime_DB1
key - параметр метода
function - анонимная функция обратного вызова
error и value - параметры функции обратного вызова
Функция обратного вызова является параметром метода и разработчик не может передать значения в её параметры error и value. Эти параметры служат для возврата результатов работы метода прсредством callback и доступны только для чтения. За пределами фиолетового (или желтого) блока данные параметры будут игнорироваться, либо возвращать значение undefined (неопределенно).
Функции обратного вызова также используются в желтых блоках обработки событий. Предположим, в проект был добавлен блок обработки нажатия на кнопку. Когда приложение запускается на выполнение этот блок не выполняется. Он выполнится только в том случае, если пользователь нажмёт на соответствующую этому блоку кнопку.
Мы рассмотрели ассинхронный механизм работы при помощи функции обратного вызова, а теперь посмотрим это на практике.
Для демонстрации синхронного режима работы добавьте на экран кнопку btnCheck, TextInput, Switch и Column1 c кнопкой btnRun1, при нажатии на которую будет происходить создание ста копий кнопок.
Запустите этот проект и нажмите на кнопку btnRun1. Кнопка зависает в нажатом состоянии. Это говорит о том, что выполнение цикла заблокировало обновление информации на экране. Попробуйте во время выполнения цикла нажимать на кнопку btnCheck. Не нажимается. А на Switch? На iOS переключение происходит. Попробуйте ввести текст. На iOS клавиатура появляется, и при первом вводе символа цикл клонирования завершает свою работу и показывает на экране кнопки.
Выходит, при выполнении на iOS операции клонирования нажатие на кнопку блокируется, а работа Switch и TextInput не блокируется.
Попробуем использовать таймер с однократным выполнением (задержка 1 мс) и разобраться с тем, что происходит. Для этого добавьте счётчики counter и counter2
После нажатия на кнопку btnRun1 она вернулась в ненажатое состояние из-за того, что цикл копирования запустился не моментально, а с некоторой задержкой, благодаря которой кнопка на экране успела перерисоваться.
В момент клонирования нажмите несколько раз на кнопку btnCheck и несколько раз переключите Switch. После вывода на экран всех клонов надпись на кнопке btnCheck будет изменяться до тех пор, пока не отобразит количество нажатий на неё, а Switch (на iOS) несколько раз переключит своё состояние. В момент клонирования кнопок обработка событий от пользователя работает, но они не обрабатываются сразу, а помещаются в очередь событий. После завершения работы цикла, события последовательно выбираются из очереди для выполнения назначенных для них действий.
Обновите приложение (сдвинув любой внешний блок в поле редактора блоков) и в момент клонирования кнопок введите текст в поле TextInput. Это действие не окажет никакое влияния на Android, а на iOS произойдёт прерывание работы цикла и на экране отобразится столько кнопок, сколько система их успела создать, что говорит о более высоком приоритете пользовательского ввода благодаря которому можно прервать долго выполняющуюся операцию.
Как же решить проблему с задержкой обновления информации на экране? Для этого необходимо в цикл добавить задержку (0,01 секунды) при помощи блока wait seconds. Благодаря ей следующая итерация цикла будет запущена после небольшой паузы, что даст системе время выполнить другие операции и обновить экран. Не пытайтесь при помощи блока waite seconds исправлять ошибки в блоках при работе с данными. Для получения данных из Интернета (при работе с Web API, Airtable, Firebase и др.) данные поступают не мгновенно, а с некоторой задержкой. При этом нельзя гарантироват то, что данные вообще поступят, как считают многие пользователи по умолчанию, пытаясь работать с web-средой, как с массивом данных, которые доступны в момент запуска приложения.
При этом может получиться так, что блок, который находится ниже, выполнится раньше блока, расположенного перед ним. Если нижнему блоку нужны данные, которые поступают в верхний блок, то он ничего не получит, потому что выполняется раньше верхнего блока. Для устранения этой проблемы перед нижним блоком программист ставит блок с задержки waitseconds, что приводит к разным проблемам.
Рассмотрим простой пример.
На первый взгляд кажется то, что всё хорошо. Сначала мы указали звуковой файл, затем он начинает воспроизводиться, а после этого выполняется блок do something. Проблема в том, что если загружаемый файл большой, а скорость загрузки недостаточно высока, то блок do something выполнится раньше блока Play, который может вообще не выполниться, если при получении звукового файла возникла ошибка. Если бы механизм работы был синхронным, то при возникновении ошибки в блоке Play приложение просто зависло, а в нашем асинхронном случае ничего не произойдёт и приложения будет работать дальше. Это вторая причина, почему современные приложения строятся на основе асинхронного механизма работы. Если пользователь долгое время не будет видеть активности приложения, то ему покажется, что оно зависло, сделано плохо и лучше воспользоваться другим приложением, которое работает лучше и дружественно по отношению к нему.
Запомните, если в последующих блоках нужно использовать данные, запрашиваемые в фиолетовом блоке с секцией do, то их необходимо всегда размещать в секции do этого фиолетового блока. Это будет являться гарантией того, что блоки в секции do не выполнятся раньше фиолетового блока.
Но с нашим примером не всё так просто. В отличие от других асинхронных блоков, блок Sound имеет особенность - воспроизведение звукового файла будет произведено только в случае его успешной загрузки в приложение. Для этого блок Sound.Play необходимо расположить в блоке Sound.onSourceLoaded. Как быть, если файл нужно воспроизвести при нажатии на кнопку? Для этого нужно добавить переменную, значение которой укажет на то, успешно ли произошла загрузка файла. Переменные, которые предназначены для индикации успешного выполнения каких-то действий в приложении, часто называют флагами. Это связано с военными операциями, когда поднятый флаг служил сигналом успешности выполнения предыдущей операции и/или для начала выполнения следующей операции.
Ниже показаны блоки для надёжной работы с компонентом Sound.
Почему блок on source loaded и on error реализованы в виде событий, а не включены в блок Play?. Это сделано для получения более гибкого решения. Если бы эти блоки были включены в блок Play, то в этом случае они будут жестко привязаны к этому блоку. Но, например, ошибка может возникнуть не только при выполнении блока Play. Поместите блок обработки ошибки в каждый метод. А что делать, если ошибка возникнет при выполнении действия, для которого не определён блок? Тогда мы не сможем её обработать. Для устранения такой ситуации блок on error выполнен в виде блока обработки события. Что касается блока on source loaded, то он также реализован в виде блока обработки события, потому что результат его работы влияет на возможность использования любого метода плеера. Не имеет смысла вызывать методы плеера, если при загрузке звукового файла возникла ошибка.
Если данные поступают в приложение из любых внешних источников, то перед выполнением любой операции над ними необходимо убедиться в том, что эти данные были загружены в приложение без ошибок, и имеют корректный формат. Для проверки корректности формата данных существует множество различных web-сервисов, а для контроля успешности загрузки данных необходимо использовать либо специально предназначенные блоки – when Sound on source loaded, whenMaponMapReady, whenCanvasloads и другие, в случае их отсутствия, проверять доступность данных в блоках перед их использованием.
Линейный порядок выполнения блоков можно представить как единственную дорогу. Но в реальных приложениях часто возникает необходимость осуществить проверку чего-то и в зависимости от её результатов выполнить разные операции. Например, если пользователь ввёл название товара, то показать в области списка только те товары из каталога, которые соответствуют этому названию. В таких случаях используют разветвляющийся порядок выполнения блоков и блок If, с которым мы познакомимся на следующем занятии.