Какие рекомендации по State Machine в js, основанные на личном опыте?

Пишу “жонглирование” запросами в браузере: когда второй запрос зависит от результатов первого. При это первый может “упасть” или, теоретически, его можно попробовать отправить заново или его могут отменить, или второй запрос нужно отменить когда начинается новый первый. В целом получается лапша. При этом в UI хочу отображать статусы обоих. После того как написал кучу кода, понял что по сути пытаюсь реализовать StateMachine. Внимание вопрос - кто может посоветовать библиотеки-подходы для реализации state machine, основанные на своем опыте.

Привет, @dmitry, вот посмотри сюда: https://rxjs-dev.firebaseapp.com/ в твоем случае, это https://golosay.net/rxjs-subjects/
Вот наглядный перечень операторов: https://angularfirebase.com/lessons/rxjs-quickstart-with-20-examples/

1 лайк

Как мне это поможет? RXJS - замена state machine? Или там есть продвинутые примеры связанных запросов? Например когда есть 2 запроса. Отправка второго зависит от результатов первого. При этом первый можно “перезапрашивать” и второй нужно отменять если новый первый запрошен с другими параметрами. При этом обрабатываются как серверные так и runtime ошибки.

Как вариант можно пайпить запросы, соответсвенно через Observable. Через forkJoin собирать до кучи и retry делать, если вдруг что или отменять. Обрабатывать последовательно и через subscribe - получать данные. Через map (например) можно обработать все входные данные и пропагейтить подписчикам. Соответсвенно, ты можешь управлять всем флоу асинхронных запросов имея ссылки на них в общем pipe. Посмотри последнюю ссылку.
А вот логирование runtime errors тут уже можно и https://sentry.io подключить и туда складывать данные при обработке ошибок, как в runtime так и requests. А если нужен конкретный State Management Machine:

  1. https://github.com/mobxjs/mobx-state-tree
  2. https://redux.js.org/
1 лайк

У нас на проекте на сервере используется. Не моя часть, насколько удачный модуль не скажу.

Можно еще рассмотреть вариант получения объекта состояния для запроса, а не результата каждого запроса. Т.е. запрос, который влияет на состояние в ответ получает текущее состояние.

На первом проекте на такое наткнулся, тоже пытался отслеживать. Так делать не надо. Закончится плохо.

Для наглядности масштаба трагедии:

async load() {
    const {store} = this
    this.resetPendingRequests()
    this.resetRequestsResolutionData()
    store.setCoreDataWaitingState(true)
    store.setReservationsDataWaitingState(true)
    trackEvent('User', 'Team scope use', store.featuresScope)
    trackEvent('User', 'Graph request', store.nodeLabel)
    try {
        const res = await getGraphData({
            nodeId: store.rootNodeId,
            labelId: store.nodeLabel,
            labelTitle: store.selectedLabelTitle
        })
        if (res.success) {
            store.setGraphCoreData(res.data)
            store.setCoreDataWaitingState(false)
            const reservationDescriptor = getReservationsInfo({emails: getEmailIds(res.data.nodes)})
            reservationDescriptor.promise
                .then(({success, data, message, cancelled}) => {
                    if (success) {
                        store.setReservations(respReservationToStoreReservation(data))
                    } else if (cancelled) {
                        // it's fine
                        store.setReservationsDataFailureReason({
                            reason: 'cancelled',
                            message: 'Cancelled'
                        })
                    } else {
                        store.setReservationsDataFailureReason({
                            reason: 'server',
                            message: message
                        })
                    }
                })
                .finally(() => {
                    store.setReservationsDataWaitingState(false)
                })
            this.pendingExtraData.push(reservationDescriptor)
        } else {
            // cancellation is not implemented for graph data request
            store.setCoreDataFailureReason({
                reason: 'server',
                message: res.message
            })
            store.setReservationsDataWaitingState(false)
            trackEvent('Tech', 'Render failure')
        }
    } catch (e) {
        store.setCoreDataFailureReason({
            reason: 'runtime',
            message: `Application runtime error "${e.toString()}"`
        })
        store.setReservationsDataWaitingState(false)
        trackEvent('Tech', 'Render failure')
        // propagate error further to handle it on global level, as this error is not expected
        throw e
    } finally {
        store.setCoreDataWaitingState(false)
    }
}

У библиотеки есть экспорт в формат который можно визуализировать через GraphViz. Думаю очень крутая штука https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/states-and-transitions.md. Буду пробовать.

Из минусов - тайпингзы только для версии 2.4, а библиотека уже 3.0.1 с явно измененными интерфейсами.

Написал решение на state machine, заметки описал тут Какие заметки я сделал, описывая связанные AJAX запросы через State Machine

1 лайк

Будет поздновато, ну может пригодиться в будущем, то посмотрите в сторону xstate, написана парнем из Майкрософта и активно используется у них

Вопрос бессрочный. Годный проект. Спасибо