레이블이 JavaScript인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JavaScript인 게시물을 표시합니다. 모든 게시물 표시

2024년 12월 28일 토요일

자바스크립트 동기 비동기 처리 정리

 

자바스크립트는 기본적으로 동기 방식으로 작동한다. 즉, 코드가 위에서 아래로 순차적으로 실행된다.

하지만, 자바스크립트는 비동기 처리가 필요한 상황(예: 네트워크 요청, 타이머, 파일 읽기 등)에서 비동기 처리를 할 수 있도록 콜백, 프라미스(Promises), async/await와 같은 기능을 제공하여 비동기 방식으로 작업할 수 있게 한다.

자바스크립트의 비동기 처리는 이벤트 루프(Event Loop)를 통해 이루어지며, 이를 통해 메인 스레드를 차단하지 않고 비동기 작업이 완료될 때까지 기다린 후 실행을 이어갈 수 있다.

자바스크립트에서 여러 함수를 순차적으로 호출할 때, 기본적으로는 각 함수가 이전 함수의 실행이 완료될 때까지 기다리지 않는다. 즉, 자바스크립트는 한 함수가 끝나기 전에 다음 함수를 호출할 수 있다. 하지만 이 동작 방식은 함수 내부의 작업이 동기식인지 비동기식인지에 따라 다르다.

  1. 동기 함수: 함수가 동기적으로 작성된 경우, 자바스크립트는 해당 함수가 완전히 실행될 때까지 기다렸다가 다음 함수를 실행한다. 즉, 이전 함수가 완료되지 않으면 다음 함수로 넘어가지 않는다.
  2. 비동기 함수: 함수 내부에 비동기 코드가 포함된 경우(예: setTimeout, fetch, Promise 등), 해당 함수는 비동기 작업이 완료되기를 기다리지 않고 바로 다음 함수를 호출한다. 비동기 작업이 완료되면 나중에 콜백 또는 Promisethen 블록에서 결과를 처리한다.

예를 들어, 다음 코드에서는 fetchData()가 비동기 함수라면 processData()가 바로 실행된다.

function fetchData() {
  setTimeout(() => console.log("Data fetched"), 1000); // 1초 후 실행
}

function processData() {
  console.log("Processing data");
}

fetchData(); // 비동기 함수
processData(); // 즉시 실행됨

이 코드에서는 fetchData가 완료되기를 기다리지 않고 processData가 바로 실행된다. 만약 순차적으로 실행되기를 원한다면, Promiseasync/await를 사용하여 비동기 함수를 동기적인 방식으로 제어할 수 있다.

자바스크립트에서 함수의 동기/비동기 동작 여부는 함수가 비동기 작업을 포함하는지에 따라 결정된다. 즉, setTimeout, Promise, async/await 등의 비동기 처리가 포함되어 있는지가 동기/비동기 동작을 구분하는 기준이다.

결과를 반환하지 않는 동기 함수는 여전히 호출된 순서대로 실행을 마치고 다음 코드로 넘어가기 때문에 동기적으로 동작한다.

예시

아래의 calculateSum 함수는 결과를 반환하지 않지만, for 루프를 통해 합계를 계산한 후 console.log로 결과를 출력한다. 이 함수는 순차적으로 실행되며, 완료될 때까지 다음 코드로 넘어가지 않는다.

function calculateSum(arr) {
  let sum = 0;
  for (let num of arr) {
    sum += num;
  }
  console.log("Sum:", sum); // 결과를 출력
}

calculateSum([1, 2, 3, 4, 5]);
console.log("이 문장은 합계가 출력된 후 실행된다.");

위 코드를 실행하면, calculateSum 함수가 완료된 후 "이 문장은 합계가 출력된 후 실행된다."가 출력된다. calculateSum이 결과를 반환하지 않더라도, 순차적으로 실행되므로 동기적으로 동작한다고 볼 수 있다.

따라서 동기/비동기 여부는 반환 여부와 관계없이 함수 내부에 비동기 코드가 있는지에 따라 결정된다.

자바스크립트에서 제공하는 비동기 함수 또는 비동기 처리를 지원하는 메서드는 아래와 같은 것들이 있다. 이를 이용해 시간이 오래 걸릴 수 있는 작업을 비동기적으로 처리할 수 있다.


타이머 관련 비동기 함수

  1. setTimeout 지정된 시간이 지난 후 콜백 함수를 실행한다.
  2. setInterval 지정된 간격마다 콜백 함수를 반복 실행한다.
  3. clearTimeoutsetTimeout으로 설정된 작업을 취소한다.
  4. clearIntervalsetInterval로 설정된 작업을 취소한다.

네트워크 요청 관련 함수

  1. fetch

    HTTP 요청을 보내고 응답을 비동기적으로 처리한다.

    fetch("<https://api.example.com/data>").then(response => response.json());
    
    
  2. XMLHttpRequest

    옛날 방식으로 HTTP 요청을 처리하는 객체로, 비동기 호출 지원.

    const xhr = new XMLHttpRequest();
    xhr.open("GET", "<https://api.example.com/data>", true);
    xhr.send();
    
    
  3. WebSocket

    서버와 실시간 통신을 위한 비동기 API.

    const ws = new WebSocket("wss://example.com/socket");
    ws.onmessage = (event) => console.log(event.data);
    
    
  4. Axios

    외부 라이브러리지만, 널리 사용되는 Promise 기반 HTTP 요청 라이브러리.

    axios.get("<https://api.example.com/data>").then(response => console.log(response));
    
    

이벤트 관련 비동기 처리

  1. DOM Event Listeners

    DOM 이벤트를 비동기로 처리.

    document.addEventListener("click", () => console.log("Clicked!"));
    
    
  2. EventEmitter (Node.js)

    이벤트 기반 비동기 처리를 위한 객체.

    const EventEmitter = require("events");
    const emitter = new EventEmitter();
    emitter.on("event", () => console.log("Event triggered"));
    emitter.emit("event");
    
    

Promise 기반 비동기 함수

  1. Promise

    비동기 작업의 성공 또는 실패를 처리하는 객체.

    const promise = new Promise((resolve, reject) => {
      setTimeout(() => resolve("Done"), 1000);
    });
    
    
  2. Promise.all

    여러 Promise를 병렬로 처리하고, 모두 완료되면 결과를 반환한다.

  3. Promise.race

    여러 Promise 중 가장 먼저 완료된 하나의 결과를 반환한다.

  4. Promise.allSettled

    모든 Promise의 상태(성공/실패)를 반환한다.

  5. Promise.any

    여러 Promise 중 가장 먼저 성공한 결과를 반환한다.


Async/Await

  1. async/awaitPromise를 쉽게 다룰 수 있도록 지원하는 ES8 구문.

    async function fetchData() {
      const response = await fetch("<https://api.example.com/data>");
      return await response.json();
    }
    
    

타이머 및 스케줄링 관련 (Node.js)

  1. process.nextTick 이벤트 루프의 다음 틱에서 실행.

  2. setImmediate 이벤트 루프의 다음 반복 주기에서 실행.

  3. queueMicrotask 마이크로태스크 큐에 작업을 추가.

    queueMicrotask(() => console.log("Microtask executed"));
    
    

File I/O 및 데이터 처리 (Node.js)

  1. fs.readFile

    파일을 비동기적으로 읽음.

    const fs = require("fs");
    fs.readFile("file.txt", "utf-8", (err, data) => console.log(data));
    
    
  2. fs.writeFile

    파일을 비동기적으로 씀.


실시간 작업 및 워커 관련

  1. setTimeout/setInterval (실시간 작업)

  2. Web Workers

    스레드에서 실행되는 비동기 작업.

    const worker = new Worker("worker.js");
    worker.onmessage = (event) => console.log(event.data);
    
    
  3. SharedWorker

    여러 스크립트 간 공유되는 워커.

  4. Service Workers

    웹 애플리케이션의 백그라운드 작업 처리.


애니메이션 및 UI 관련

  1. requestAnimationFrame

    브라우저에서 애니메이션 작업을 효율적으로 처리하기 위해 비동기로 실행.

    requestAnimationFrame(() => console.log("Animation frame"));
    
    
  2. cancelAnimationFrame

    예약된 애니메이션 프레임을 취소.


기타 비동기 처리

  1. setTimeout/setInterval

  2. queueMicrotask

  3. process.nextTick (Node.js 전용)

  4. IntersectionObserver

    요소가 뷰포트에 진입하거나 나갈 때 비동기적으로 알림.

  5. MutationObserver

    DOM 변경 사항을 비동기로 감지.

    const observer = new MutationObserver((mutations) => console.log(mutations));
    observer.observe(document.body, { childList: true });
    
    
  6. FileReader

    파일을 비동기적으로 읽는 Web API.

    const reader = new FileReader();
    reader.onload = (event) => console.log(event.target.result);
    reader.readAsText(someFile);
    
    
  7. Geolocation API

    위치 정보를 비동기로 가져옴.

    navigator.geolocation.getCurrentPosition((position) => console.log(position));
    
    
  8. Notification

    사용자에게 비동기로 알림을 표시.

2024년 8월 14일 수요일

마우스 클릭과 더블클릭 구분하여 더블클릭 방지 처리 : JavaScript ES6 React

click 이벤트와 dblclick 이벤트를 사용하고, 클릭을 setTimeout으로 일정 시간 동안 지연시켜 더블클릭인지 단일 클릭인지 확인하는 방법을 사용한다.

JavaScript로 작성한 후 React 버전으로 수정 작성하였다.

모듈 파일 (handlers.js)

setupDblClickHandler 함수는 더블 클릭 시 클릭 타이머를 정리하는 역할만 수행한다.

// handlers.js

const clickTimeouts = new WeakMap();

export function setupClickHandler(element, handleClick) {
    element.addEventListener("click", function(event) {
        clearTimeout(clickTimeouts.get(element)); // 기존 타이머 클리어
        const timeout = setTimeout(function() {
            handleClick(event);
        }, 500); // 500ms 후에 클릭 이벤트로 처리
        clickTimeouts.set(element, timeout); // 새로운 타이머 저장
    });
}

export function setupDblClickHandler(element) {
    element.addEventListener("dblclick", function() {
        clearTimeout(clickTimeouts.get(element)); // 기존 타이머 클리어
    });
}

메인 파일 (main.js)

// main.js

import { setupClickHandler, setupDblClickHandler } from './handlers.js';

document.addEventListener("DOMContentLoaded", function() {
    // 예제 핸들러 함수
    function exampleClickHandler(event) {
        alert(`Single Click Detected on element: ${event.target.id}`);
    }

    // 요소를 동적으로 선택
    var button1 = document.getElementById("myButton1");
    var button2 = document.getElementById("myButton2");

    // 동적 핸들러를 각 요소에 설정
    setupClickHandler(button1, exampleClickHandler);
    setupClickHandler(button2, exampleClickHandler);
    setupDblClickHandler(button1);
    setupDblClickHandler(button2);
});

HTML 파일

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Element and Handler</title>
</head>
<body>
    <button id="myButton1">Click or Double Click Me</button>
    <button id="myButton2">Another Button</button>

    <script type="module" src="main.js"></script>
</body>
</html>

코드 설명

  1. setupClickHandler: 클릭 이벤트가 발생하면 타이머를 설정하여 단일 클릭 이벤트를 처리한다.
    • 이전 타이머를 정리하고 새로운 타이머를 설정하여 500ms 후에 handleClick 함수를 호출한다.
  2. setupDblClickHandler: 더블 클릭 이벤트가 발생하면 타이머를 정리한다.
    • 클릭 타이머를 정리하여 단일 클릭 이벤트가 호출되지 않도록 한다.
  3. exampleClickHandler: 단일 클릭 이벤트 핸들러로, 클릭된 요소의 ID를 alert 창에 표시한다.
  4. HTML 파일: main.jstype="module"로 포함하여 모듈 시스템을 사용한다.

주의 사항

  • 이 구조에서는 더블 클릭 시 추가 로직을 처리하지 않는다. 만약 더블 클릭에 다른 동작을 추가하고 싶다면, setupDblClickHandler 함수를 다시 확장해야 한다.
  • 최신 브라우저 환경에서 작동하도록 설계되었기 때문에 구형 브라우저에서는 ES6 모듈과 WeakMap 지원을 확인해야 한다.

React에서 사용 가능하도록 수정한 버전 1

React에서 setupClickHandlersetupDblClickHandler 기능을 제공하는 방법으로, 커스텀 훅을 사용하여 단일 클릭과 더블 클릭을 구분하는 로직을 구현할 수 있다. 커스텀 훅은 재사용 가능한 로직을 추상화하고 컴포넌트에서 쉽게 사용할 수 있도록 도와준다.

아래는 useClickHandler라는 커스텀 훅을 작성하고 이를 React 컴포넌트에서 사용하는 예시이다.

1. useClickHandler 커스텀 훅 작성

hooks/useClickHandler.js

단일 클릭과 더블 클릭을 처리할 수 있는 useClickHandler 훅을 작성한다. 이 훅은 clickHandler를 인자로 받아서 클릭 이벤트와 더블 클릭 이벤트를 처리한다.

// hooks/useClickHandler.js

import { useRef, useEffect } from 'react';

export function useClickHandler(clickHandler) {
    const clickTimeoutRef = useRef(null);

    useEffect(() => {
        // 타이머를 클리어하는 함수
        const clearClickTimeout = () => {
            if (clickTimeoutRef.current) {
                clearTimeout(clickTimeoutRef.current);
                clickTimeoutRef.current = null;
            }
        };

        const handleClick = (event) => {
            clearClickTimeout();
            clickTimeoutRef.current = setTimeout(() => {
                clickHandler(event);
            }, 300); // 300ms 후에 클릭 이벤트로 처리
        };

        const handleDblClick = () => {
            clearClickTimeout(); // 더블 클릭 시 타이머 클리어
        };

        return { handleClick, handleDblClick };
    }, [clickHandler]);

    return { handleClick, handleDblClick };
}

2. React 컴포넌트에서 훅 사용

useClickHandler 훅을 사용하여 클릭 이벤트와 더블 클릭 이벤트를 처리하는 예제 컴포넌트를 작성한다.

components/ClickHandlerComponent.js

// components/ClickHandlerComponent.js

import React from 'react';
import { useClickHandler } from '../hooks/useClickHandler';

export function ClickHandlerComponent() {
    const { handleClick, handleDblClick } = useClickHandler((event) => {
        alert(`Single Click Detected on element: ${event.target.id}`);
    });

    return (
        <div>
            <button
                id="myButton1"
                onClick={handleClick}
                onDoubleClick={handleDblClick}
            >
                Click or Double Click Me
            </button>
            <button
                id="myButton2"
                onClick={handleClick}
                onDoubleClick={handleDblClick}
            >
                Another Button
            </button>
        </div>
    );
}

3. 앱 컴포넌트

React 앱에서 ClickHandlerComponent를 사용.

App.js

// App.js

import React from 'react';
import { ClickHandlerComponent } from './components/ClickHandlerComponent';

function App() {
    return (
        <div className="App">
            <h1>React Click and Double Click Handler</h1>
            <ClickHandlerComponent />
        </div>
    );
}

export default App;

4. 프로젝트 구조

/project-root
    ├── /src
    │    ├── /components
    │    │    └── ClickHandlerComponent.js
    │    ├── /hooks
    │    │    └── useClickHandler.js
    │    └── App.js
    ├── index.html
    └── index.js

코드 설명

  1. useClickHandler:
    • clickTimeoutRef를 사용하여 클릭 타이머를 관리한다.
    • handleClickhandleDblClick 함수는 각각 클릭과 더블 클릭 이벤트를 처리한다.
    • clearClickTimeout 함수는 타이머를 클리어하여 단일 클릭과 더블 클릭을 구분한다.
    • useEffectclickHandler가 변경될 때마다 새로 등록된 핸들러를 업데이트 한다.
  2. ClickHandlerComponent 컴포넌트:
    • useClickHandler 훅을 사용하여 클릭 및 더블 클릭 핸들러를 생성한다.
    • 각 버튼에 handleClickhandleDblClick을 이벤트 핸들러로 설정한다.
  3. App 컴포넌트:
    • ClickHandlerComponent를 사용하여 버튼 클릭 이벤트를 처리합니다.

추가 사항

  • 의존성 관리: useClickHandler 훅은 clickHandler가 변경될 때마다 타이머를 관리하도록 의존성을 설정한다.
  • 타이머 관리: 타이머는 useRef로 관리되어 컴포넌트가 리렌더링되더라도 상태가 유지된다.

React에서 사용 가능하도록 수정한 버전 2

React에서 이벤트 핸들러를 설정하고 단일 클릭과 더블 클릭을 구분하기 위해, Hook을 사용하여 상태를 관리할 수 있다. 아래 예시에서는 useEffectuseRef를 사용하여 DOM 이벤트를 설정하고, useState를 사용하여 클릭 타이머 상태를 관리한다.

1. Custom Hook (useClickHandler.js)

먼저, 클릭과 더블 클릭 핸들러를 설정하는 Custom Hook을 정의한다.

// useClickHandler.js

import { useEffect, useRef } from 'react';

const useClickHandler = (elementRef, handleClick) => {
  const clickTimeoutRef = useRef(null);

  useEffect(() => {
    const element = elementRef.current;

    const handleSingleClick = (event) => {
      clearTimeout(clickTimeoutRef.current);
      clickTimeoutRef.current = setTimeout(() => {
        handleClick(event);
      }, 300); // 300ms 후에 클릭 이벤트로 처리
    };

    const handleDoubleClick = (event) => {
      clearTimeout(clickTimeoutRef.current); // 더블 클릭 시 타이머 클리어
    };

    if (element) {
      element.addEventListener('click', handleSingleClick);
      element.addEventListener('dblclick', handleDoubleClick);
    }

    // Cleanup 이벤트 리스너
    return () => {
      if (element) {
        element.removeEventListener('click', handleSingleClick);
        element.removeEventListener('dblclick', handleDoubleClick);
      }
    };
  }, [elementRef, handleClick]);
};

export default useClickHandler;

2. 컴포넌트 파일 (MyComponent.js)

Custom Hook을 사용하여 단일 클릭 이벤트 핸들러를 설정하는 컴포넌트를 작성한다.

// MyComponent.js

import React, { useRef } from 'react';
import useClickHandler from './useClickHandler';

const MyComponent = () => {
  const buttonRef1 = useRef(null);
  const buttonRef2 = useRef(null);

  const handleClick = (event) => {
    alert(`Single Click Detected on element: ${event.target.id}`);
  };

  useClickHandler(buttonRef1, handleClick);
  useClickHandler(buttonRef2, handleClick);

  return (
    <div>
      <button id="myButton1" ref={buttonRef1}>Click or Double Click Me</button>
      <button id="myButton2" ref={buttonRef2}>Another Button</button>
    </div>
  );
};

export default MyComponent;

코드 설명

  1. Custom Hook (useClickHandler.js):
    • useClickHandlerelementRefhandleClick을 인자로 받는다.
    • clickTimeoutRefuseRef를 사용하여 클릭 타이머를 저장한다.
    • useEffect를 사용하여 컴포넌트가 마운트될 때 이벤트 리스너를 설정하고 언마운트될 때 정리한다.
    • 단일 클릭 이벤트를 처리하는 handleSingleClick과 더블 클릭 시 타이머를 정리하는 handleDoubleClick을 정의한다.
  2. 컴포넌트 파일 (MyComponent.js):
    • buttonRef1buttonRef2는 각각 두 버튼의 참조를 저장한다.
    • handleClick은 단일 클릭 이벤트 핸들러로, 클릭된 요소의 idalert로 표시한다.
    • useClickHandler를 사용하여 각 버튼에 대해 클릭 핸들러를 설정한다.
    • 버튼 요소에 ref 속성을 설정하여 DOM 요소를 참조할 수 있게 한다.

React 애플리케이션 구조

/src
    /components
        MyComponent.js
    /hooks
        useClickHandler.js
    index.js

React 애플리케이션 설정

index.js

애플리케이션의 진입점으로, MyComponent를 렌더링.

import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from './components/MyComponent';

ReactDOM.render(
  <React.StrictMode>
    <MyComponent />
  </React.StrictMode>,
  document.getElementById('root')
);

관련 사이트 및 블로그