10.02 항해 20일차 TIL

한우석·2021년 10월 3일

강의를 들으며 비동기 통신에 대한 이해가 잘 되지 않아서 다른 자료들을 조금 더 찾아 보게 되었다.
그럼에도 아직 명확한 이해가 되지 않아 자유롭게 쓰는게 힘들지만 어느정도 개념은 잡은 것 같아서 오늘 배운 개념들을 한번 정리 해보았다.


동기(Synchronous) VS 비동기(Asynchronous) 통신

자바스크립트는 싱글스레드 기반으로 프로세스를 처리한다. 그러므로 기본적으로는 동기방식으로 진행이 된다.
동기로직과 비동기로직은 브라우저 시스템 곳곳에 적용되어 자바스크립트에서는 이를 매우 일반적으로 사용하고 있다.
하지만 웹이 점점 고도의 앱으로 변화함에 따라 순수한 자바스크립트만의 로직도 무거워져 비동기화된 로직을 직접 구현해야 할 상황에 이르렀다.

  • 차이점

    • 요청의 결과값이 리턴값과 동일한 것이 동기, 요청의 결과값이 리턴값과 다른 것이 비동기이다.
    • 명령이 끝날때 까지 사용자에게 제어권을 돌려 주지 않는 것이 동기, 명령이 끝나기전 사용자에게 제어권을 돌려 주는 것이 비동기이다.

Point! 어떤 루틴이 끝날때까지 제어권이 돌아오지 않으면 동기식 그렇지 않으면 비동기식인 것이다.

동기, 비동기의 원래 의미는 통신에서 상대방의 일정 신호에 의해서 다음 동작이 이루어지면 동기, 상대방의 상태와 관계없이 일방적으로 동작하면 비동기 이다.

즉, 상대방이 받을준비 됐다는 신호를 받아서 한byte 보내고,수신측에서 한byte받은 후 또 보내도 된다는 신호를 보내고 이 신호 확인후 보내고.. 이런식이 동기식이다.

이에 반하여 비동기에서는 일단 한번 전송 시작되면 좌~악 보낸다. 이와 비슷한 개념으로 일반 software에서 동기식이라 함은 어떤 루틴을 완전히 끝내고 제어를 반납하면 동기식, 동작이 안 끝났어도 일단 제어권을 반납한 후 지 할일 계속하면 비동기식이다.


  • JS Engine에는 프로세스가 실행될 Memory Heap과 그 순서를 담은 Call Stack이 있다.
  • Web API는 Browser에서 DOM을 조작하기 쉽도록 하는 여러 메소드를 제공한다.
  • Event Loop는 TaskQueue(CallbackQueue)와 Call Stack을 지속적으로 관찰하며 CallStack이 비었을 때 Task Queue를 비워간다.

이러한 흐름을 JS EventLoop라고 한다.


콜백

콜백은 자바스크립트가 비동기 처리를 하기 위한 패턴 중 하나이다.
전통적인 콜백 패턴은 일명 콜백 헬로 불리는 엄청난 중첩 문제가 생기기 쉽다.

콜백 헬
꼬리에 꼬리를 무는 비동기 처리가 늘어나면 호출이 계속 중첩되고, 코드가 깊어지고, 관리는 어려워진다. 이런 깊은 중첩을 콜백 헬 이라고 부른다.

function async1('a', function (err, async2){
	if(err){
		errHandler(err);
	}else{
		...
		async2('b', function (err, async3){
			...
		}){
			...
		}
	}
});

이런 콜백 헬이 발생하는 이유?

  • 비동기 처리 시에는 실행 완료를 기다리지 않고 바로 다음 작업을 실행한다.
  • 즉, 순서대로 코드를 쭉 적는다고 우리가 원하는 순서로 작업이 이뤄지지 않는다.
  • 비동기 처리 함수 내에서 처리 결과를 반환하는 걸로는 원하는 동작을 하지 않으니, 콜백 함수를 사용해 원하는 동작을 하게 하려고 콜백 함수를 쓴다.
  • 이 콜백 함수 내에서 또 다른 비동기 작업이 필요할 경우 위와 같은 중첩이 생기면서 콜백 헬이 생긴다.

Promise

  • 비동기 연산이 종료된 이후 결과를 알기 위해 사용하는 객체
  • Promise를 쓰면 비동기 메서드를 마치 동기 메서드처럼 값을 반환할 수 있다.
  • 전통적인 콜백 패턴으로 인한 콜백 헬 때문에 ES6에서 도입한 또다른 비동기 처리 패턴
    비동기 처리 시점을 좀 더 명확하게 표현할 수 있다
  • 프라미스는 Promise 생성자 함수를 통해 생성한다.
    비동기 작업을 수행할 콜백 함수를 인자로 전달받아서 사용한다.
// Promise 객체를 만든다. 
// 인자로는 (resolve, reject) => {} 이런 excutor 실행자를 받는다.
// 이 실행자는 비동기 작업이 끝나면 바로 두 가지 콜백 중 하나를 실행한다.
// resolve: 작업이 성공한 경우 호출할 콜백
// reject: 작업이 실패한 경우 호출할 콜백
const promise = new Promise((resolve, reject) => {
	if(...){
		...
		resolve("성공!");
	}else{
		...
		reject("실패!");
	}
});

Promise의 상태값

  • pending: 비동기 처리 수행 전(resolve, reject가 아직 호출되지 않음)
  • fulfilled: 수행 성공(resolve가 호출된 상태)
  • rejected: 수행 실패(reject가 호출된 상태)
  • settled: 성공 or 실패(resolve나 reject가 호출된 상태)

프라미스 후속 처리 메서드

  • 프라미스로 구현된 비동기 함수는 프라미스 객체를 반환한다.
  • 프라미스로 구현된 비동기 함수를 호출하는 측에서는 이 프라미스 객체의 후속 처리 메서드를 통해 비동기 처리 결과(성공 결과나 에러메시지)를 받아서 처리해야 한다.
  • .then(성공시, 실패시)
    then의 첫 인자는 성공 시 실행, 두번째 인자는 실패 시 실행된다. (첫 번째 인자만 넘겨도 된다.)
// Promise를 하나 만든다.
let promise = new Promise((resolve, reject) => {
	setTimeout(() => resolve("완료!"), 1000);
});

// resolve
promise.then(result => {
	console.log(result); // 완료!가 콘솔에 출력된다.
}, error => {
	console.log(error); // 실행되지 않는다.
});


// Promise를 하나 만든다.
let promise = new Promise((resolve, reject) => {
	setTimeout(() => reject(new Error("오류!")), 1000);
});

// reject
promise.then(result => {
	console.log(result); // 실행되지 않는다.
}, error => {
	console.log(error); // Error: 오류!가 출력된다.
});
  • .catch(실패 시)
// 프라미스를 하나 만든다.
let promise = new Promise((resolve, reject) => {
	setTimeout(() => reject(new Error("오류!"), 1000);
});

promise.catch((error) => {console.log(error};);

Promise Chaining

  • 프라미스는 후속 처리 메서드를 체이닝해서 여러 개의 프라미스를 연결할 수 있다. (이걸로 콜백 헬을 해결할 수 있다.)
    • 후속 처리 메서드 (then)을 이어 주면 된다.
new Promise((resolve, reject) => {
	setTimeout(() => resolve("promise 1"), 1000);
}).then((result) => { // 후속 처리 메서드 하나를 쓰고,
	console.log(result); // promise 1
	return "promise 2";
}).then((result) => { // 이렇게 연달아 then을 써서 이어준다.
	console.log(result);
	return "promise 3";
}).then(...);

async, await

promise 패턴의 단점은 아직도 나아지지 않은 callback Hell과 이해해야 할 복잡한 개념이 많다는 것이다.

Async-Await는 이런 문제점들을 잡아주고, 형재는 비동기 통신의 비교적 완성형으로 널리 사용되고있다.

하지만, 역시 Promise 객체를 기반으로 동작한다.

이 둘이 세트인 이유는 Async로 선언되어야만 Await로 동기와 비동기를 구분지을 수 있게 되기 때문에 바늘가는데 실가듯 항상 같이 따라오는 것이다.

위의 소개된 것들과 같이 역시나 이것도 단점은 존재하는데, JQuery처럼 잘못 사용하면 병목현상을 유발 할 수 있다는 점이다.

  • async
    • 함수 앞에 async를 붙여서 사용한다.
    • 항상 promise를 반환 한다. (promise가 아닌 값이라도, promise로 감싸서 반환 해준다.)
// async는 function 앞에 써준다.
async function myFunc() {
	return "프라미스를 반환해요!"; // 프라미스가 아닌 걸 반환
}

myFunc().then(result => {console.log(result)}); // 콘솔로 확인하면 promise로 들어온다.
  • await
    • async의 짝꿍이다. (async 없이는 못쓴다.)
    • async 함수 안에서만 동작한다.
    • await는 promise가 처리될 때까지 기다렸다가 그 이후에 결과를 반환한다.

async function myFunc(){
	let promise = new Promise((resolve, reject) => {
		setTimeout(() => resolve("완료!"), 1000);
	});

    console.log(promise);

	let result = await promise; // 여기서 기다리자!하고 신호를 준다.

    console.log(promise);

	console.log(result); // then(후처리 함수)을 쓰지 않았는데도, 1초 후에 완료!가 콘솔에 출력된다.
}

await를 만나면, 실행이 잠시 중단되었다가 promise 처리 후에 실행을 재개한다.
즉, await를 쓰면 함수 실행을 기다리게 해서 코드를 콜백 헬로 빠지지 않게 한다.

profile
H/W 개발자에서 프론트 엔드 개발자로 전향 하고 있는 초보 개발자

0개의 댓글