동기,비동기

수민·2022년 11월 24일
0

프론트엔드

목록 보기
43/48

💡동기(Synchronous) vs. 비동기(Asynchronous)

동기(Synchronous)는 순서가 존재한다.
그리고 하나의 작업이 끝나기 전까진 다른 작업을 진행할 수 없다.

실생활에서 카페에서의 주문을 예로 들어보면,
우리가 카페에 가서 커피를 주문을 한다고 가정해보면,

점원에게 커피 주문
점원은 주문 접수
커피제작
고객에게 완성된 커피 전달
이 순서로 진행될 것 이다.
다만, 이 상황을 동기적으로 처리한다고 하면

주문이 접수되고, 커피를 만들고 고객에게 전달할 때까지 다른 고객의 주문을 받을 수가 없다.
그렇게 되면, 다른 고객은 긴 시간동안 대기를 해야할 것이고, 그 카페에 엄청나게 긴 줄이 생기게 될 것이다.

이 문제를 해결하기 위해선 비동기(Asynchronous) 방식을 사용해야 한다.

비동기(Asynchronous)는 순서가 존재하지 않는다.
그리고 완료되는 순서대로 리턴된다.

카페에서의 상황을 비동기적으로 진행시켜보면,

주문이 접수됨과 동시에 A의 커피는 제작에 들어간다.
그리고, B의 주문도 접수가 되고 A커피와는 별개로 B커피 제작이 진행된다.

그리고 A,B커피 중 먼저 완성되는 커피가 고객에게 전달된다.
나머지 커피도 완성되는대로 고객에게 전달된다.

이렇게 되면, 고객이 불필요하게 대기할 필요가 없어지고, 좀 더 효율적으로 빠르게 카페가 운영될 수 있다.

👌서버의 관점에서 동기와 비동기를 본다면

동기의 경우,

client가 server에게 어떤 데이터를 요청하면, server는 데이터를 불러오기 위한 작업을 진행할 것이고,
데이터 불러오기 작업이 완료되면, client에게 데이터를 제공한다.
하지만, server가 작업하는 동안 client는 대기상태에 들어가고 다른 작업을 수행하지 못한다.

비동기의 경우,

client가 server에게 데이터를 요청하면, server에 요청이 접수되고 데이터를 불러오기 위한 작업이 실행된다.
이 때, client는 대기상태가 아닌, 하던 작업을 계속 이어 나간다.
그리고, server측에서 작업을 완료하고 데이터를 응답해주면, client는 그 응답을 받고 새로운 작업을 하던, 계속 기존 작업을 이어가던 할 것이다.

😢자바스크립트에서의 동기와 비동기

자바스크립트는 Synchronous이다.
그 뜻은, 호이스팅이 된 이후부터 코드 작성 순서대로 실행된다는 것이다.

우리는 동기로 만들어진 자바스크립트를 비동기처럼 사용하기 위해서, 다양한 방식을 사용하는데
바로, Callback, promise, Async/await 이다.

🔍Callback


function printImmediately(callback){
	callback();
}

console.log('coding');
printImmediately(() => console.log('hello'));

// coding
// hello

위의 코드는 동기적으로 위에서 순차적으로 실행되고 있다.
만약, 콜백함수로 비동기적으로 실행을 시키고 싶을 땐 어떻게 해야할까?

function printWithDelay(callback, timeout){
	setTimeout(callback, timeout);
}

console.log('coding');
printWithDelay(() => console.log('Async callback'), 3000);
printImmediately(() => console.log('hello'));

// coding
// hello
// Async callback (3초 뒤)

이처럼 setTimeout함수를 활용하여 실행을 비동기적으로 처리할 수 있다.
(setTimeout 함수는 ms단위의 시간값을 인자로 받아, 콜백함수가 몇초 뒤에 실행되게 할 지 설정할 수 있다.)

📌Callback hell
콜백함수와 setTimeout 함수를 사용하여 비동기적인 처리를 할 수 있지만, 콜백함수로 많은 처리를 하다보면, 아래와 같은 코드를 보게 된다.

위와 같이 가독성이 떨어지고, 에러가 발생했을 경우 디버깅하기에도 쉽지않아 유지보수하기 어렵다는 문제가 발생하게 된다.

🔍Promise

Promise도 Callback과 마찬가지로 비동기적으로 처리하기 위해 사용한다.
Promise의 상태(state)에는 3가지가 있다.

  • pending : operation이 수행중 일 때
  • fullfilled : operation 수행이 완료되었을 때
  • rejected : operation에 문제가 발생했을 때
    그렇다면 Promise를 어떻게 사용할까?
const promise = new Promise((resolve, reject) => {
	console.log('doing something');  
});



먼저, promise를 생성해줘야 한다.
promise는 클래스이기 때문에 new키워드로 새로운 객체를 만들어준다.

이 때, 새로운 프로미스가 생성될 때, 우리가 전달한 콜백함수가 자동으로 바로 실행된다.
그렇기때문에, 위의 코드처럼 작성할 경우, 우리가 원하지 않은 상황에 원치않는 결과를 받게 될 수 있다.

이 문제를 해결하기 위해 promise객체의 파라미터로 받는 resolve와 reject를 사용하게 된다.

  • resolve : 성공 (fullfilled)
  • reject : 실패 (rejected)
const promise = new Promise((resolve, reject) => {
	console.log('doing something');  
  	setTimeout(() => {
    	resolve('do it');
    }, 2000);
});

만약, 우리가 전달한 콜백함수가 정상적으로 실행되었다면, resolve에 전달된 'do it'이라는 문자열이 리턴된다.

그렇지 않고, 정상적으로 실행되지 않았을 때,

const promise = new Promise((resolve, reject) => {
	console.log('doing something');  
  	setTimeout(() => {
    	reject(new Error('no network'));
    }, 2000);
});


reject부분이 실행이 되면서, 에러메시지를 리턴한다.

위와 같이 promise를 생성했고, 그렇다면 이 promise를 어떻게 사용할 수 있을까?

  • .then
  • .catch
  • .finally
    이 3개의 키워드를 통해 promise에서 값을 받아올 수 있다.
const promise = new Promise((resolve, reject) => {
	console.log('doing something');  
  	setTimeout(() => {
    	resolve('do it');
    }, 2000);
});


promise.then(value => {
	console.log(value);
});

then은 콜백함수가 정상적으로 실행됐을 때 즉, resolve의 리턴값을 받아오게 된다.

위의 코드를 보면 resolve의 전달인자에 'do it'이 들어가있으므로, then일 경우, 'do it' 문자열이 then의 value에 전달되게 되고, 최종적으로 'do it'이 출력되게 된다.

const promise = new Promise((resolve, reject) => {
	console.log('doing something');  
  	setTimeout(() => {
    	reject(new Error('no network'));
    }, 2000);
});
promise.catch(error => {
	console.log(error);
});

만약 정상적이지 않게 처리되었을 경우, catch키워드를 사용하여 에러를 처리할 수 있다.
reject의 전달인자가 catch의 error에 전달되게 되고, 최종적으로 에러메시지를 출력하게 되는 것이다.

promise.finally(() => {
	console.log('finally');
});


finally의 경우, 콜백함수가 성공했든 실패했든 상관없이 마지막에 실행되는 부분이라고 생각하면 된다.

🔍Promise all

여러개의 promise를 병렬적으로 처리하고 싶을 때, promise에서 제공하는 API중 하나인 promise all을 사용하면 된다.

const pickFruit = ()=>{
	return Promise.all([getApple(), getBanana()])
      .then(fruits => fruits.join( '+' ));
}

위의 코드처럼, Promise.all()의 파라미터에 배열의 형태로 여러개의 Promise를 전달한다.
그렇게 되면, 각 프로미스의 PromiseResult는 .then의 fruits에 배열의 형태로 전달되게 되고, 우리는 그 배열을 입맛대로 가공하여 사용할 수 있다.

🔍Async / await

Promise도 .then이 많아지다보면, 콜백지옥과 같은 가독성 떨어지는 코드가 만들어질 수 있다.
Async / await는 조금 더 가독성 좋게, 효율적으로 작성하는 방법이다.
(하지만, 그렇다고해서 무조건 promise가 안좋고 Async/await 방식이 좋다는 것은 아니다.)

const delay = (ms) => {
	return new Promise(resolve => setTimeout(resolve, ms));
}

const getApple = async() => {
	await delay(3000);
  	return 'apple';
}

const getBanana = async() => {
	await delay(3000);
  	return 'banana';
}

위의 코드처럼, 함수 앞에 async를 붙히게 되면, 함수의 코드블럭이 promise로 바뀌게 된다.
즉, 함수의 리턴값이 promise객체가 된다는 것이다.

그리고, await은 반드시 뒤에 promise를 리턴하는 함수가 와야하는데, 그 await뒤의 함수가 실행될 때가지 기다려줌으로써
비동기적인 실행을 가능하게 해준다.

Async / await을 쓰면 위의 코드처럼, 동기적인 것처럼 코드가 작성되지만 비동기적으로 실행되게 할 수 있다는 장점이 있다.

참고블로그:https://velog.io/@hun0613/TIL%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0

profile
헬창목표

0개의 댓글