Vuex:在调度操作之前等待 websocket 响应

分享于2022年07月17日 javascript node.js vue.js vuex websocket 问答
【问题标题】:Vuex:在调度操作之前等待 websocket 响应(Vuex: Wait for websocket response before dispatching action)
【发布时间】:2022-02-21 18:22:27
【问题描述】:

所以这是场景/前提:

  • 为了实时填充聊天队列,我需要打开到 websocket 的连接,发送消息,然后将数据设置到 websocket 存储。这个 store 基本上会管理所有的 websocket 状态。
  • 在填充聊天队列之前,我需要两个参数:来自一个 http API 请求的 shiftId 和来自 websocket 的 connectionId。使用这两个参数,我终于可以订阅第三个 http API 并开始接收消息以填充聊天队列。

问题是由于 websocket 的异步行为(或者这就是我的想法,如果我错了,请纠正我)当我尝试将 put 放到那个“订阅”API。我尝试过使用 async/await 和 promise,但似乎没有任何效果。我对 Vuex 的 async/await 和 websockets 还很陌生,所以我很确定我做错了什么。

这是用户 vuex 模块,我在其中执行所有登录/令牌操作并从 shift vuex 模块发送“updateEventsSubscription”操作。为了使“updateEventsSubscription”操作起作用,我需要从“processWebsocket”操作(获取 connectionId 参数)和来自 shifts vuex 模块的“startShift”操作(获取 shiftId 参数)获取响应:

import UserService from '@/services/UserService.js'
import TokenService from '@/services/TokenService.js'
import router from '@/router'
export const namespaced = true
export const state = {
    accessToken: '',
    errorMessage: '',
    errorState: false,
    userEmail: localStorage.getItem('userEmail'),
    userPassword: localStorage.getItem('userPassword'),
}
export const mutations = {
    SET_TOKEN(state, accessToken) {
        state.accessToken = accessToken
        TokenService.saveToken(accessToken)
    },
    SET_USER(state, authUserJson) {
        state.userEmail = authUserJson.email
        state.userPassword = authUserJson.password
        localStorage.setItem('userPassword', authUserJson.password)
        localStorage.setItem('userEmail', authUserJson.email)
    },
    SET_ERROR(state, error) {
        state.errorState = true
        state.errorMessage = error.data.error_description
    },
    CLOSE_NOTIFICATION(state, newErrorState) {
        state.errorState = newErrorState
    },
}
export const actions = {
    signIn({ commit, dispatch, rootState }, authUserJson) {
        return UserService.authUser(authUserJson)
            .then((result) => {
                commit('SET_USER', authUserJson)
                commit('SET_TOKEN', result.data.access_token)
                dispatch('token/decodeToken', result.data.access_token, {
                    root: true,
                })
                dispatch(
                    'shifts/updateEventsSubscription',
                    rootState.token.agentId,
                    {
                        root: true,
                    }
                )
                router.push('/support')
            })
            .catch((error) => {
                console.log(error)
                if (error.response.status === 400) {
                    commit('SET_TOKEN', null)
                    commit('SET_USER', {})
                    commit('SET_ERROR', error.response)
                } else {
                    console.log(error.response)
                }
            })
    },
    signOut({ commit }) {
        commit('SET_TOKEN', null)
        commit('SET_USER', {})
        localStorage.removeItem('userPassword')
        localStorage.removeItem('userEmail')
        TokenService.removeToken()
        router.push('/')
    },
    closeNotification({ commit }, newErrorState) {
        commit('CLOSE_NOTIFICATION', newErrorState)
    },
}
export const getters = {
    getToken: (state) => {
        return state.accessToken
    },
    errorState: (state) => {
        return state.errorState
    },
    errorMessage: (state) => {
        return state.errorMessage
    },
    isAuthenticated: (state) => {
        return state.accessToken
    },
    userEmail: (state) => {
        return state.userEmail
    },
    userPassword: (state) => {
        return state.userPassword
    },
}

这是 websocket 商店:我将 connectionId 传递给 state,以便能够在另一个 vuex 操作中使用它来订阅新聊天:

export const namespaced = true
export const state = {
    connected: false,
    error: null,
    connectionId: '',
    statusCode: '',
    incomingChatInfo: [],
    remoteMessage: [],
    messageType: '',
    ws: null,
}
export const actions = {
    processWebsocket({ commit }) {
        const v = this
        this.ws = new WebSocket('mywebsocket')
        this.ws.onopen = function (event) {
            commit('SET_CONNECTION', event.type)
            v.ws.send('message')
        }
        this.ws.onmessage = function (event) {
            commit('SET_REMOTE_DATA', event)
        }
        this.ws.onerror = function (event) {
            console.log('webSocket: on error: ', event)
        }
        this.ws.onclose = function (event) {
            console.log('webSocket: on close: ', event)
            commit('SET_CONNECTION')
            ws = null
            setTimeout(startWebsocket, 5000)
        }
    },
}
export const mutations = {
    SET_REMOTE_DATA(state, remoteData) {
        const wsData = JSON.parse(remoteData.data)
        if (wsData.connectionId) {
            state.connectionId = wsData.connectionId
            console.log(`Retrieving Connection ID ${state.connectionId}`)
        } else {
            console.log(`We got chats !!`)
            state.messageType = wsData.type
            state.incomingChatInfo = wsData.documents
        }
    },
    SET_CONNECTION(state, message) {
        if (message == 'open') {
            state.connected = true
        } else state.connected = false
    },
    SET_ERROR(state, error) {
        state.error = error
    },
}

最后这是轮班商店(问题出在哪里),如您所见,我有一个 startShift 操作(一切正常),然后是“updateEventsSubscription”,我试图等待来自的响应“startShift”动作和“processWebsocket”动作。调试应用程序我意识到 startShift 操作一切正常,但是 websocket 操作在“updateEventsSubscription”需要它之后发送响应,当我尝试对该 API 进行放置时导致错误(因为它需要来自websocket 的状态)。

import ShiftService from '@/services/ShiftService.js'
export const namespaced = true
export const state = {
    connectionId: '',
    shiftId: '',
    agentShiftInfo: '{}',
}
export const actions = {
    startShift({ commit }, agentId) {
        return ShiftService.startShift(agentId)
            .then((response) => {
                if (response.status === 200) {
                    commit('START_SHIFT', response.data.aggregateId)
                }
            })
            .catch((error) => {
                console.log(error)
                if (error.response.status === 401) {
                    console.log('Error in Response')
                }
            })
    },
    async updateEventsSubscription({ dispatch, commit, rootState }, agentId) {
        await dispatch('startShift', agentId)
        const shiftId = state.shiftId
        await dispatch('websocket/processWebsocket', null, { root: true })
        let agentShiftInfo = {
            aggregateId: state.shiftId,
            connectionId: rootState.websocket.connectionId,
        }
        console.log(agentShiftInfo)
        return ShiftService.updateEventsSubscription(shiftId, agentShiftInfo)
            .then((response) => {
                commit('UPDATE_EVENTS_SUBSCRIPTION', response.data)
            })
            .catch((error) => {
                if (error.response.status === 401) {
                    console.log('Error in Response')
                }
            })
    },
}
export const mutations = {
    START_SHIFT(state, shiftId) {
        state.shiftId = shiftId
        console.log(`Retrieving Shift ID: ${state.shiftId}`)
    },
    UPDATE_EVENTS_SUBSCRIPTION(state, agentShiftInfo) {
        state.agentShiftInfo = agentShiftInfo
    },
}

  • 可能是题外话,但我看到密码已保存到 localStorage。也许这是一个安全风险?通常,accessToken 或类似的东西会被持久化,而不是敏感的密码信息。
  • 感谢@Kunukn 的评论,会调查的。

【解决方案1】:

您应该将您的 WebSocket 操作转换为当 WebSocket 连接时解析的承诺。:

export const actions = {
    processWebsocket({ commit }) {
      return new Promise(resolve=> {
        const v = this
        this.ws = new WebSocket('mywebsocket')
        this.ws.onopen = function (event) {
            commit('SET_CONNECTION', event.type)
            v.ws.send('message')
              resolve();
        }
        this.ws.onmessage = function (event) {
            commit('SET_REMOTE_DATA', event)
        }
        this.ws.onerror = function (event) {
            console.log('webSocket: on error: ', event)
        }
        this.ws.onclose = function (event) {
            console.log('webSocket: on close: ', event)
            commit('SET_CONNECTION')
            ws = null
            setTimeout(startWebsocket, 5000)
        }
     });
    },
}

  • 感谢@Eldar,但在按照您的建议进行操作后,我仍然得到一个空的连接 ID。当我在浏览器上检查网络分流时,订阅 API 的请求有效负载是 {"connectionId": ""} 这意味着当 API 需要时,connectionId 仍然不存在。我是否必须对“updateEventsSubscription”进行任何更改?
  • @Lowtrux 我认为 connectionId: rootState.websocket.connectionId 应该是=> connectionId: rootState.websocket.ws.connectionId, 因为 websocket 指的是状态, ws 指的是实际Websocket 的属性。
  • 不是@Eldar。 rootState.websocket.connectionId 中的 websocket 引用了 vuex 模块“websocket”中的 connectionId 属性,它工作正常,一切都被命名了。