18-1. 동기와 비동기 정의18-1-1. 동기18-1-2. 비동기18-2. 가장 많이 사용하는 비동기 처리18-2-1. call back18-2-2. promise18-2-2-1. Thenable18-2-2-2. Promise.all()18-2-2-3. Promise.race()18-2-2-4. for-awit-of18-2-3. async/await18-3. 병렬 비동기 처리란18-3-1. Web Workers18-3-2. SharedArrayBuffer18-3-3. Atomics
18-1. 동기와 비동기 정의
18-1-1. 동기
동기(Synchronous) 코드는 순차적으로 실행되며, 한 번에 하나의 작업만 처리한다. 코드 한 줄이 실행되면 그다음 줄이 실행된다. 다른 작업이 완료될 때까지 현재 작업을 멈추고 기다린다. 이러한 동작은 코드의 진행을 차단하고, 어떤 작업이 끝나길 기다려야 할 때 문제가 될 수 있다.
18-1-2. 비동기
비동기(Asynchronous) 코드는 작업을 백그라운드에서 병렬로 처리하며, 기다리지 않고 코드 실행을 계속할 수 있다. 이벤트 루프를 통해 비동기 작업이 완료되면 콜백 함수나 promise를 통해 처리할 수 있다.
18-2. 가장 많이 사용하는 비동기 처리
가장 많이 사용하는 비동기처리 시스템을 알아보도록 하자, 대표적으로 call back, promise, async/await가 있다.
18-2-1. call back
콜백은 한 작업이 끝나고 다음 작업에 들어가는 가장 근본적인 방법이다.
위 코드를 보면 알 수 있지만, 작업1 함수 안에 작업2를 넣어서, 작업1을 실행하고 나서 작업2를 실행하게 만든다. 만일 이러한 비동기 코드를 만들었을 때 수백 개의 코드가 있다면 유지보수가 어려워질 것이다.
18-2-2. promise
promise는 콜백에서 문제점을 제거하기 위한 패턴이다. then을 이용해서 함수 이후에 실행할 작업을 관리해, 함수 안에 중첩된 함수가 있는 경우를 제어할 수 있다.
promise를 사용하면 위처럼 함수 안에 함수가 있는 것을 방지할 수 있다. then() 이후의 함수를 실행한다. catch()는 에러가 뜰 때만 발생하고 inally() 함수가 끝나거나 에러가 뜰 때, 마지막에 무조건 한번 실행된다.
18-2-2-1. Thenable
thenable은 가독성을 위해 사용하는 함수이다.
Promise 객체는 성공, 실패, 현재 진행 상태와 같은 세 가지 상태를 갖는다. then()메서드를 사용하여 Promise의 결과를 처리할 수 있다. 이때, thenable은 then()메서드를 가지고 있어 Promise와 유사한 방식으로 동작할 수 있는 객체를 가리킨다.
18-2-2-2. Promise.all()
Promise.all() 함수는 병렬처리를 하기 위해 사용한다. Promise.all()은 여러 개의 Promise을 동시에 실행하고, 모든 Promise가 성공적으로 완료될 때까지 기다린 후 결과를 배열로 반환한다. 만약 하나의 Promise라도 실패하면 첫 번째로 실패한 Promise의 결과 또는 에러를 반환한다.
18-2-2-3. Promise.race()
여러 개의 Promise를 대기하고, 그중 하나가 먼저 완료되면 해당 Promise의 결과 또는 에러를 반환하는 메서드이다. 이 메서드는 여러 개의 비동기 작업 중에서 가장 먼저 끝난 작업의 결과만 필요할 때 유용하다.
Promise.race() 메서드의 구문은 다음과 같다.
- iterable: Promise 객체의 배열 또는 이터러블(iterable) 객체를 받는다. 여러 개의 Promise를 이 배열 또는 이터러블에 전달하고, 그중 하나가 완료될 때까지 대기한다.
- 주어진 Promise 중 하나라도 먼저 완료되면 해당 Promise의 결과 또는 에러를 반환한다.
- 나머지 Promise는 계속 백그라운드에서 실행되지만, 그 결과는 무시된다.
- 반환된 Promise는 가장 먼저 완료된 Promise에 대한 결과 또는 에러를 가지게 된다.
18-2-2-4. for-awit-of
JavaScript에서 비동기 반복을 처리하기 위한 구문이다. 기본적으로 for-await-of는 비동기적으로 반복 가능한 객체를 동기적으로 반복하면서 await키워드를 사용하여 각 항목을 처리할 수 있도록 해준다. 병렬처리는 보통 순차적으로 실행하지 않는다 일반적으로 for-await-of는 비동기적으로 처리해야 하는 작업을 담은 Promise 객체의 배열 또는 비동기적으로 데이터를 가져올 수 있는 Iterable 객체를 반복할 때 사용된다. 이 구문은 for…of와 유사하나, 각 항목을 처리할 때 await을 사용할 수 있어 비동기 코드를 더욱 간결하게 작성할 수 있다. 다음은 for-await-of의 기본 구문과 예제이다.
race는 최초의 로직만 실행한다면 for-awit-of 로직은 모든 로직을 실행한다.
18-2-3. async/await
위 코드는 await로 모든 것을 제어한다. 이에 따라서 코드를 수정할 때 다른 함수에서 찾아갈 필요 없이 간단하게 정리할 수 있다. 단, 이때 async를 function 앞에 달아야 한다. 하지만 무리하게 await를 사용할 경우 라이브러리 등 다른 함수에서 가져온 함수가 비동기를 지원하지 않아서 사용할 수 없을 때 아무런 거부감 없이 사용하면 오히려 자주 에러가 뜰 수도 있다.
18-3. 병렬 비동기 처리란
아래에는 병렬 비동기 처리, Web Workers, SharedArrayBuffer, 그리고 Atomics에 관한 간단한 설명과 예시 코드이다.
병렬 비동기 처리: 병렬 비동기 처리는 여러 개의 비동기 작업을 동시에 실행하고, 모든 작업이 완료될 때까지 기다리지 않고 결과를 수집하는 것을 의미한다. 대표적으로 Promise.all() 이 있다.
18-3-1. Web Workers
Web Workers는 웹 애플리케이션에서 병렬로 실행되는 백그라운드 스레드를 생성하고 사용하는데 도움을 주는 기술이다. 아래는 Web Worker를 사용하여 병렬로 스크립트를 실행하는 간단한 예시 코드이다.
위 로직은 isMainThread에서 현재가 메인 스레드인지 판단한다. 메인 스레드는 화면을 그릴 때 사용하는 스레드를 말한다.
위와 같이 Worker을 생성한다. Worker는 백그라운드에서 실행하며 메인 스레드와 별개의 스레드로 실행하는 로직이다. 이로써 화면을 그리고 있는 cpu가 독립적으로 활동 가능하다.
위에 worker_threads에 저장시켜 둔 workerData를 가져오는 방법이다.
worker.on은 백그라운드 스레드에서 통신이 온 것을 받는다. parentPort.on은 부모 스레드에서 통신이 온 것을 받는다. 각각의 통신은 .postMessage을 사용해서 소통한다.
18-3-2. SharedArrayBuffer
SharedArrayBuffer는 여러 웹 워커 사이에서 메모리를 공유하는 데 사용된다. SharedArrayBuffer를 사용하여 데이터를 워커 간에 공유하고 동기화할 수 있다.
SharedArrayBuffer은 하나의 Worker를 여러 로직에 사용할 때 사용한다.
18-3-3. Atomics
Atomics 객체는 SharedArrayBuffer와 함께 멀티 스레딩 환경에서 사용된다. SharedArrayBuffer는 여러 로직이 사용하지만 다른 스레드에서 변경을 했을 때 공유 중인 worker 파일이 원자성을 보장받지 못해 그것을 보장하기 위해 Atomics 나왔다.
원자성이란 다른 로직이 변경하는 명령을 하여도 변경되지 않고 원래 자신값을 가지는 것을 의미한다.