JS 비동기&동기 처리 (+callBack 함수& Promise & async/await 문법 )

woolee의 기록보관소·2022년 11월 10일
0

FE개념정리

목록 보기
8/35

자바스크립트는 항상 synchronous 처리를 한다

자바스크립트는 브라우저 환경이 아니더라도 사용이 가능하지만 기본적으로 웹 페이지용 스크립트 언어이다. 그리고 일종의 lightweight, interpreted, object-oriented programming language이다.

자바스크립트는 항상 동기식(synchronous) 처리를 하며, 단일 스레드 언어(single-threaded language)이다.

단일 스레드이므로 한번에 하나의 명령만 처리할 수 있고 다른 명령은 현재 실행 중인 명령이 실행되기 전까지는 실행을 기다려야 한다. 즉 코드를 한줄씩 차례대로 실행한다. 그리고 synchronous는 synchronous라는 이름에서 이미 암시하듯, sequence 상태에 있음(one at a time)을 말한다.

다만, 자바스크립트도 비동기식(asynchronous) 처리가 가능하다. 예를 들어 자바스크립트의 실행 환경인 웹브라우저는 setTimeout(), addEventListner, ajax 같이 시간이 오래 걸리는 특수한 코드를 만나면 얘네들을 한쪽에 제쳐두고 다른 코드부터 실행하려고 한다. 이런 처리 방식을 비동기(asynchronous) 처리 방식이라고 한다.
(한쪽으로 제쳐두고 나중에 실행하므로 당연히 읽는 시점과 동작 시점에 차이가 있을 수밖에 없다.)

이런 비동기 처리는 자바스크립트라는 언어 자체의 기능이 아니라 자바스크립트 실행을 도와주는 웹브라우저 덕분에 가능한 것이다.

요약하자면, 자바스크립트는 싱글 스레드 언어이므로 기본적으로 동기식 처리를 한다. 오래 걸리는 연산을 만나면 멈춘다. 그런데 비동기식 처리도 가능하다. 웹 API와 연관된 특수한 함수들을 쓰면 작업이 오래 걸리는데, 이때 다른 코드부터 실행할 수 있다.

callback function과 callback hell

자바스크립트에서 web API와 같은 특수한 함수를 만나면 비동기적 상황에 처하게 되는데, 이러한 비동기적 상황에서 코드를 순차적으로 실행하고 싶을 때(동기적으로 처리하고 싶어서) 콜백함수를 사용한다.

인자로 함수를 넣으면 그게 콜백함수가 된다.

아래처럼 코드를 작성하면, 첫번째 함수를 실행해서 첫번째함수의 코드를 실행한 뒤, 콜백함수로 두번째함수를 불러와서 두번째 함수의 코드를 실행한다.

여기에 web API 관련 함수들을 넣으면 비동기적 상황에서도 동기적으로 동작하게 코드를 작성할 수 있다.

function 첫번째함수(callback) {
	// 첫번째함수 코드 
	callback();
}
function 두번째함수() {
	// 두번째함수 코드 
}

첫번째함수(두번째함수);

하지만 콜백함수의 문제점은 콜백지옥.
계속해서 함수를 들여쓰다 보면, 코드가 옆으로 길어지면서 코드 가독성이 떨어지고 예외 처리를 하기가 복잡해지는 문제점이 있다.

첫번째함수(function(){
  두번째함수(function(){
    세번째함수(function(){
		// 옆으로 계속 늘어나는 현상 
    });
  });
}):

Promise

비동기적 상황에서 동기적으로 처리하고 싶은데 콜백지옥에 빠지고 싶지 않을 때 ES6 신문법인 Promise 문법을 사용하면 된다.

then을 통해 코드가 이어지므로 가독성이 높아진다.
catch를 통해 오류가 발생했을 때의 예외 처리를 하기가 쉽다.

첫번째함수()
.then(function () {
  // 성공하면 
  // 다음에 실행할 코드 
})
.catch(function () {
  // 오류가 발생하면 
})

Promise를 보다 자세히 들여다 보자면,

프로미스에는 3가지 상태가 존재한다.
-- Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
-- Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
-- Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

new Promise()로 프로미스를 생성하면 Pending(대기) 상태가 되며 콜백함수의 인자로 resolve, reject를 받는다.

new Promise(function(resolve, reject) {
  // ...
});

콜백함수 인자인 resolve를 실행하면 Fulfilled(이행) 상태가 된다. 그리고 이행 상태가 되면 위에서 보듯 .then() 이용해서 결과 값을 받을 수 있다.

function 함수() {
  return new Promise(function(resolve, reject) {
    let result = '결과값';
    resolve(result);
  });
}

// resolve()의 결과 값 result를 resolvedData로 받는다
함수().then(function(resolvedData) {
  console.log(resolvedData); // 결과값
});

반대로 reject()를 호출하면 프로미스는 Rejected(실패) 상태가 된다. 그리고 실패 상태가 되면 실패한 이유를 catch()로 받아볼 수 있다.

function 함수() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받는다.
함수().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

Async/Await

async/await 문법은 ES8(2017)에 추가된 문법으로, promise 문법을 보다 쉽게 다룰 수 있게 해주는 문법이다.

async 키워드를 함수 앞에 붙여주면 Promise 오브젝트가 된다.
new Promise()를 사용하지 않아도 async 키워드만 붙이면 .then을 사용할 수 있는 것이다.

async function 함수 (){
	//
}

함수()
  .then(function(result) {
  	console.log(result) });

.then 대신에 await이라는 키워드를 사용하면 보다 쉽게 결과값 result를 다룰 수 있다. 변수에 바로 결과값을 삽입할 수 있다.

async function 함수 (){
  let 결과값 = new Promise((resolve, reject) => {
    let result = 'result';
    resolve(result);
  });
  let result = await 결과값;
  
  return result;
}

함수(); // 결과값 

다만, await은 에러가 나면 코드가 멈춘다. 에러가 나도 코드를 멈추게 하고 싶지 않으면 try catch 문법을 사용하면 된다. try {} 내부 코드가 멈추면 대신 catch {} 내부 코드를 실행해준다.

async function 함수 (){
  let 결과값 = new Promise((resolve, reject) => {
    let result = 'result';
    resolve(result);
  });
  try { let result = await 결과값; } 
  catch { /*실패했을 때의 코드 작성*/ }
  
  return result;
}

함수(); // 결과값 

물론 async/await은 promise보다 사용하기 간편하지만, 매개변수의 생명주기가 promise보다 길어서 위험할 수 있다.

아래 예제 코드를 살펴보면,
promise에서는 hello 변수가 하나의 then 절에서 끝나고 이후에는 쓰이지 않는다. 반면 async await에서는 hello가 한번 더 사용되는 걸 볼 수 있다.

// promise chaining
function test () {
  return doSomethingAsync ()
    .then(hello => doAsync2(hello))
    .then(world => doAsync3(world))
    .then(foo => doAsync4(foo))
    .then(bar => doAsyncLast(bar));
}

// async await
async function test2 () {
  const hello = await doSomethingAsync();
  const world = await doAsync2(hello);
  const foo = await doAsync3(world);
  const bar = await doAsync4(foo);
  await doAsyncLast(bar);
}

어쨌든 요약하자면

자바스크립트는 항상 동기적으로 처리하며 가끔 비동기 실행을 지원하는 특수한 함수들(web API)을 만나면 비동기적으로 실행된다.

callback 함수든, promise든 이들은 단지 코드 혹은 함수에 대한 하나의 디자인 패턴일 뿐이다.

참고

Is Javascript Synchronous or Asynchronous?

코딩애플 - 콜백함수가 뭔지 한국어로 쉽게 설명하는 영상

자바스크립트 Promise 쉽게 이해하기

async await, 정말 좋은데... 이게 왜 좋은걸까요?

profile
https://medium.com/@wooleejaan

0개의 댓글