밝을희 클태

웹소켓 연결 비동기 문제 해결 본문

PongWorld 프로젝트/트러블 슈팅

웹소켓 연결 비동기 문제 해결

huipark 2024. 3. 10. 20:00

문제 :

전체채팅 페이지에서 한 유저를 골라서 1 : 1 채팅으로 넘어가서 새로고침시 대화방의 내용이 다시 렌더링이 안된다.

다른 페이지에서 웹소켓을 연결을 하고 채팅방 페이지로 오면 문제가 없지만 채팅방 페이지에서 새로고침해서 최초로 웹소켓 연결시 문제가 발생

 

사실 수집:

문제 재현 과정에서 WebSocket 연결 메시지 송수신 과정에서 비동기 처리의 순서 문제를 확인. 새로고침 웹소켓 재연결 과정에서 이전 채팅 내용을 fetch하기 전에 웹소켓으로 메시지를 보내려 하였으나, 웹소켓 연결이 완료되지 않은 상태였음.

 

원인 추론:

await checkConnectionSocket(this.socketEventHendler.bind(this));
const $chatRoom = document.querySelector('.chatRoom');
this.$chatRoom = $chatRoom;

if (this.params.user) {
  this.target = Number(this.params.user);
  this.sendWebsocket();
}

await checkConnectionSocket() 완료되기 전에 this.sendWebsocket() 실행되어, 웹소켓 연결이 완료되기 전에 데이터를 요청하는 로직이 실행됨. 이로 인해 웹소켓 연결 완료 이전에 이전 채팅 내용을 불러오는 과정에 문제가 발생함.

 

조치 방안 및 구현:

WebSocket 연결이 완료될 때까지 기다린 , 연결 완료 이후에 필요한 데이터 요청 로직을 실행하도록 Promise async/await 사용해 비동기 처리 순서를 제어. WebSocket onopen 이벤트에서 Promise resolve() 호출하여 연결 완료를 알리고, 이후 로직이 실행되도록 수정함.

 

결과 관찰:

수정 새로고침 시에도 이전 채팅 내용이 정상적으로 렌더링되는 것을 확인. 웹소켓 연결 상태를 정확히 관리하며 비동기 처리 순서가 보장되어 문제가 해결됨.

 

코드:

// WebSocket
import BaseWebSocket from './BaseWebSocket.js';
import {getToken, refreshAccessToken} from '../tokenManager.js';

class ConnectionSocket extends BaseWebSocket {
  static instance = null;

  constructor() {
    super();
    this.reconnectTimer = null;
  }

  async connect(url) {
    console.log('Connection WebSocket 연결 시도중');
    super.connect(url);

    return new Promise(async (resolve, reject) => {
      this.ws.onopen = () => {
        console.log('Connection WebSocket 연결 성공!');
        resolve();
      };

      this.ws.onclose = () => {
        console.log('Connection WebSocket 닫힘');
      };
    });
  }

  setEvent(handler) {
    if (handler) {
      this.ws.onmessage = e => {
        handler(JSON.parse(e.data));
      };
    }
  }

  static getInstance() {
    if (!ConnectionSocket.instance) {
      ConnectionSocket.instance = new ConnectionSocket();
    }
    return ConnectionSocket.instance;
  }
}

const cws = ConnectionSocket.getInstance();
export default cws;
// WebSocketManager
import cws from './WebSocket/ConnectionSocket.js';
import {refreshAccessToken, getToken} from './tokenManager.js';

export const checkConnectionSocket = async handler => {
  return new Promise(async (resolve, reject) => {
    if (!cws.getWS()) {
      try {
        await connectionSocketConnect(handler);
        console.log('connectionSocket 재연결!');
        resolve();
      } catch (error) {
        console.log('checkConnectionSocket Error : ', error);
        reject(error);
      }
    } else resolve();
    cws.setEvent(handler);
  });
};

export const connectionSocketConnect = async handler => {
  if (!getToken().length) await refreshAccessToken();
  await cws.connect('ws://127.0.0.1:8000/ws/connection/');
  cws.setEvent(handler);
};

connect 함수가 실행되면, WebSocket 클래스 내에서 onopen 이벤트 핸들러에 의해 resolve 함수가 호출될 때까지 실행 흐름은 대기 상태에 들어간다. 이 과정에서 promiseasync/await 구문을 활용하여 비동기 작업의 완료를 기다리는 방식으로 구현되어 있다. 따라서, connect 함수 및 이와 관련된 상위 함수들은 내부적으로 설정된 비동기 작업이 모두 완료될 때까지 순차적으로 실행이 보류된다. 이러한 구조는 비동기 작업의 동기적 처리를 가능하게 하여, 코드 실행의 순서와 일관성을 보장한다.