Вызов служб¶
Выражение поведения всего приложения на одном автомате может быстро стать сложным и громоздким. Естественно (и поощряется!) использовать несколько автоматов, которые обмениваются данными друг с другом для реализации сложной логики. Это очень похоже на модель актора, где каждый экземпляр автомата считается «актором», который может отправлять и получать события (сообщения) другим «акторам» (например, обещаниям или другим машинам) и реагировать на них.
Чтобы автоматы могли взаимодействовать друг с другом, родительский автомат вызывает дочерний и прослушивает события, отправленные с дочернего автомата через sendParent(...)
, или ожидает, пока дочерний автомат достигнет своего конечного состояния, что вызовет onDone
переход.
Вы можете вызвать:
- Промисы, которые будут выполнять переход
onDone
при разрешенииresolve
или переходonError
при отклоненииreject
. - Функции обратного вызова, которые могут отправлять события и получать события от родительской машины
- Наблюдаемые, которые могут отправлять события на родительский автомат, а также сигнализировать о его завершении
- Автоматы, которые также могут отправлять и получать события, а также уведомлять родительский автомат, когда он достигает своего конечного состояния.
Свойство invoke
¶
Вызов определяется в конфигурации узла состояния с помощью свойства invoke
, значением которого является объект, содержащий:
src
— источник вызываемой службы, который может быть: : - автомат : - функция, которая возвращаетPromise
: - функция, которая возвращает "обработчик обратного вызова" : - функция, которая возвращает "наблюдаемого" : - строка, которая относится к любой из 4 перечисленных опций, определенных вoptions.services
данного аппарата. : - вызываемый объект источника (начиная с версии 4.12+), который содержит исходную строку в{type: src}
, а также любые другие метаданные.id
— уникальный идентификатор вызванной службыonDone
— (необязательно) переход, выполняемый, когда: : - дочерний автомат достигает своего конечного состояния, или : - вызванное обещаниеPromise
разрешается, или : - вызываемый "наблюдаемый" завершаетсяonError
— (необязательно) переход, выполняемый, когда вызываемая служба обнаруживает ошибку выполнения.autoForward
— (необязательно)true
, если все события, отправленные на этот автомат, также должны быть отправлены (или перенаправлены) вызванному дочернему автомату (по умолчаниюfalse
) : - Избегайте установкиautoForward
в значениеtrue
, так как слепая пересылка всех событий может привести к неожиданному поведению или бесконечным циклам. Всегда предпочитайте явно отправлять события или использовать создателя действияforward(...)
для прямой пересылки события вызываемому дочернему элементу (в настоящее время работает только для автомата).data
— (необязательно, используется только при вызове автоматов) объект, который отображает свойства контекста дочернего автомата на функцию, которая возвращает соответствующее значение из контекста родительского автомата.
Внимание
Не путайте свойство onDone
с состоянием invoke.onDone
— они похожи на переходы, но относятся к разным вещам.
- Свойство
onDone
на узле состояния указывает на то, что узел составного состояния достигает конечного состояния. - Свойство
invoke.onDone
относится к выполняемому вызову (invoke.src
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Вызов промисов¶
Поскольку каждый промис можно смоделировать как конечный автомат, XState может вызывать промисы "как есть". Промисы могут:
resolve()
, который примет переходonDone
reject()
(или выбросить ошибку), который примет переходonError
Если состояние, в котором активирован вызываемый промис, выходит до того, как промис разрешается, то результат промиса отбрасывается.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
Разрешенные данные помещаются в событие done.invoke.<id>
в свойстве data
, например:
1 2 3 4 5 6 7 |
|
Отклонение промиса¶
Если промис отклоняется (reject), переход onError
будет выполнен с событием {type: 'error.platform'}
. Данные об ошибках доступны в свойстве data
события:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
Внимание
Если переход onError
отсутствует и промис отклонен, то ошибка будет проигнорирована, если не указан строгий режим для автомата. В противном случае строгий режим остановит автомат и выдаст ошибку.
Вызов функций обратного вызова¶
Потоки событий, отправляемых на родительский автомат, можно моделировать с помощью обработчика обратного вызова, который представляет собой функцию, которая принимает два аргумента:
callback
— вызывается с отправляемым событиемonReceive
— вызывается с помощью слушателя, который прослушивает события от родителя
Возвращаемое (необязательное) значение должно быть функцией, которая выполняет очистку (т. е. отмену подписки, предотвращение утечек памяти и т. д.) для вызванной службы при выходе из текущего состояния. Обратные вызовы не могут использовать синтаксис async / await
, поскольку он автоматически помещает возвращаемое значение в Promise
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Прослушивание родительских событий¶
Вызванным обработчикам обратного вызова также предоставляется второй аргумент onReceive
, который регистрирует прослушиватели для событий, отправляемых обработчику обратного вызова от родителя. Это обеспечивает связь между родительским и дочерним автоматами и вызванной службой обратного вызова.
Например, родительский автомат отправляет дочерней службе ponger
событие PING
. Дочерняя служба может прослушивать это событие с помощью onReceive
(прослушиватель) и в ответ отправить событие PONG
обратно родительскому автомату:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Вызов наблюдаемых объектов¶
С версии 4.6+
Наблюдаемые объекты — это потоки значений, эмитированных с течением времени. Думайте о них как о массиве или коллекции, значения которой передаются асинхронно, а не все сразу. В JavaScript существует множество реализаций наблюдаемых объектов; самый популярный — RxJS.
"Наблюдаемые" будут отправлять события (строки или объекты) на родительский автомат, но не получать события (однонаправленные). Наблюдаемый вызов — это функция, которая принимает контекст context
и событие event
в качестве аргументов и возвращает наблюдаемый поток событий. Подписка на наблюдаемый объект отменяется при выходе из состояния, в котором он был вызван.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Вышеупомянутый intervalMachine
будет получать события из interval(...)
, сопоставленные с объектами событий, до тех пор, пока наблюдаемый объект не будет «завершен» (не завершится выдача значений). Если произойдет событие CANCEL
, наблюдаемый будет удален (.unsubscribe()
будет вызываться изнутри).
Подсказка
Наблюдаемые объекты необязательно создавать для каждого вызова. Вместо этого можно сослаться на «горячую наблюдаемую»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Вызов автоматов¶
Автоматы обмениваются данными иерархически, а вызванные автоматы могут обмениваться данными:
- От родителя к потомку — через действие
send(EVENT, {to: 'someChildId'})
- От потомка к родителю — через действие
sendParent(EVENT)
.
При выходе из состояния, в котором автомат был вызван — автомат останавливается.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
Вызов с контекстом¶
Дочерние автоматы могут быть вызваны с контекстом context
, который является производным от контекста родительского автомата context
с помощью свойства data
. Например, parentMachine
ниже вызовет новую службу timerMachine
с начальным контекстом {duration: 3000}
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Как и assign(...)
, дочерний контекст может быть отображен как объект (предпочтительно) или как функция:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Внимание
Данные data
заменяют контекст по-умолчанию context
, определенный в автомате — они не объединяются. Это поведение изменится в следующей мажорной версии.
Конечные данные¶
Когда дочерний автомат достигает своего конечного состояния, он может отправлять данные в событии done
(например, {type: 'done.invoke.someId', data: ...}
). Эти "конечные данные" указываются в свойстве data
конечного состояния:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
|
Отправка событий¶
- Чтобы отправить событие с дочернего автомата на родительский, используйте
sendParent(event)
(принимает те же аргументы, что иsend(...)
) - Чтобы отправить событие с родительского автомата на дочерний, используйте
send(event, {to: <child ID>})
Внимание
Создатели действий send(...)
и sendParent(...)
не обязательно отправляют события на машины. Это чистые функции, которые возвращают объект действия, описывающий, что нужно отправить, например {type: 'xstate.send', event: ...}
. Интерпретатор прочитает эти объекты, а затем отправит их.
Вот пример двух машин, pingMachine
и pongMachine
, которые обмениваются данными друг с другом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
|
Отправка ответов¶
Начиная с версии 4.7+
Вызванная служба (или порожденный актор) может отвечать другой службе / субъекту; то есть она может отправлять событие в ответ на событие, отправленное другой службой / актором. Это делается с помощью создателя действия response(...)
.
Например, «клиентский» автомат client
ниже отправляет событие CODE
в вызванную службу auth-server
, которая затем отвечает событием TOKEN
через 1 секунду.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
Этот конкретный пример может использовать sendParent(...)
для того же эффекта; разница в том, что response(...)
отправит событие обратно источнику полученного события, который не обязательно может быть родительским автоматом.
Множественные службы¶
Вы можете вызвать несколько служб, указав каждую в массиве:
1 2 3 4 5 6 7 |
|
Каждый вызов будет создавать новый экземпляр этой службы, поэтому даже если src
нескольких служб одинаковы (например, someService
выше), будут вызываться несколько экземпляров someService
.
Настройка служб¶
Источники вызова (службы) могут быть настроены аналогично тому, как настраиваются действия, защитные функции и т. д. — путем указания src
в виде строки и определения их в свойстве services
в параметрах автомата:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Вызов src
также можно указать как объект (начиная с версии 4.12+), который описывает источник вызова с его типом type
и другими связанными метаданными. Это можно прочитать в параметре services
в аргументе meta.src
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Тестирование¶
Указав службы в виде строк выше, "замоканные" службы можно выполнить, указав альтернативную реализацию с помощью .withConfig()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Ссылка на службы¶
Начиная с версии 4.7+
На службы (и акторов, которые являются порожденными службами) можно ссылаться непосредственно в объекте состояния из свойства .children
. Объект state.children
представляет собой сопоставление идентификаторов (ключей) службы с этими экземплярами (значениями) службы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
При сериализации JSON объект state.children
представляет собой сопоставление идентификаторов (ключей) службы с объектами, содержащими метаданные об этой службе.
Краткий справочник¶
Свойство invoke
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Вызов промисов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Вызов функций обратного вызова
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Вызов "наблюдаемых"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Вызов автоматов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|