Вызов служб¶
Выражение поведения всего приложения на одном автомате может быстро стать сложным и громоздким. Естественно (и поощряется!) использовать несколько автоматов, которые обмениваются данными друг с другом для реализации сложной логики. Это очень похоже на модель актора, где каждый экземпляр автомата считается «актором», который может отправлять и получать события (сообщения) другим «акторам» (например, обещаниям или другим машинам) и реагировать на них.
Чтобы автоматы могли взаимодействовать друг с другом, родительский автомат вызывает дочерний и прослушивает события, отправленные с дочернего автомата через 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(), который примет переходonDonereject()(или выбросить ошибку), который примет переход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 | |