JS에서의 비동기 처리_1

Suxxzzy.log·2021년 12월 12일
0

JS 비동기

목록 보기
1/3

*해당 포스트는 제가 인터넷에서 자료를 찾아보고 어떻게 이해했는지를 기술한 것입니다. 혹여 잘못된 부분을 짚어주시면 감사할 것 같습니다!

*12.19 오류 일부 수정하였습니다.

JS는 기본적으로 코드가 작성된 순서대로 실행된다

JS는 기본적으로 다음 코드의 실행 결과와 같이 코드가 적힌 순서대로 실행이 된다.

console.log(1);
console.log(2);
console.log(3);

//출력
1
2
3

따라서 중간의 코드가 처리되는데 시간 지연이 있을 경우, 그것이 완료될 때까지 기다려주지 않는다.

console.log(1);
setTimeout(console.log('2'),1000);
console.log(3);

//출력
1
3
2

이 코드를 보면 두번째 코드는 setTimeout이라는 브라우저 API를 사용하고 있는데, 1초 뒤에 콘솔에 2를 출력하는 역할을 하고 있다.
자바스크립트는 이 코드를 보고, 1초 동안 기다리지 않고, 바로 다음 코드 라인을 수행한다. 따라서 위와 같은 출력 결과를 볼 수 있다.
1초가 경과되면 console.log('2')를 수행하는 것인데, 이 함수는 약속을 수행한 후 호출된다고 해서 콜백함수라고 부른다.

콜백함수는 2가지로 나누어 생각할 수 있는데, 1. 즉각적으로 수행되는 콜백함수와 2.setTimeout과 같이 일정 시간 경과 후 수행되는 콜백함수로 나누어진다.

//즉각적으로 수행되는 콜백함수
console.log(1);
function printImmediately(func){
  func();
}
printImmediately(console.log('2'));
console.log(3);


//일정 시간 경과 후 수행되는 콜백함수
console.log(1);
function printWithDelay(func,time){
  setTimeout(func,time);
}
printImmediately(() => console.log('2'),1000);
console.log(3);

위의 경우는 1-2-3 순서로 출력되지만, 아래의 경우는 1-3-2순서로 출력된다.

언제 비동기 처리를 할까?

언제 비동기 처리를 하는지 알아야 이해하기에도 쉬울 것 같기에 정리해둔다

-setTimeout 함수의 사용
-스크립트나 모듈의 로딩

왜 promise를 사용할까?

기존의 코드들은 어떤 비동기적인 작업을 수행한 다음 성공, 실패시 각각 사용되는 콜백 함수들을 인자로 받아왔다. (아래의 코드와 같이 말이다)

function login(id, password, success, failure){
  if(id === "sue" && password === "1234"){
    success();
  }else{
    failure();
  }
}

그런데 이렇게 코드를 작성하게 되면 하나의 기능을 수행하는 함수가 2개의 인자를 더 받아와야 한다는 불편함이 있다.
또, 다른 세부 조건들을 만족시킨 후에 실행시켜야 하는 코드들도 있을 것이다. 그렇게 되면 콜백 함수 안에서 새로운 콜백 함수를 호출하여야 한다. 다음과 같이...

function login(id, password, success, failure){
  if(id === "sue" && password === "1234"){
    success(ok,no){
      if(true){
      	ok();
      }else{
        no();
      }
    };
  }else{
    failure();
  }
}

이러한 구조가 반복 작성되면, 추후 에러 핸들링도 쉽지 않고 가독성도 낮아진다는 문제점이 있다. 이를 해결하기 위해 Promise라는 개념이 등장하였다.

Promise는 무엇인가?

Promise는 위의 콜백 예제 코드를 훨씬 더 단순화하여 사용할 수 있도록 도와주는데, 기본적인 사용법은 다음과 같다.

const p = new Promise();
//변수명은 임의로 지어도 상관없다

const p = new Promise((resolve,reject) => {
  //구현 로직...
  //구현 로직이 성공적이라면 result는 value를 값으로 가진다.
  resolve(value);
  //구현 로직이 실패했다면 result는 Error객체가 전달하는 값을 갖는다.
  resolve(Error('에러 출력문구'));
})

위의 사용법 코드를 설명하면, Promise는 executor라는 콜백함수를 받는데, 이 콜백함수는 resolve와 reject 2개의 콜백 함수를 인자로 받는다. 따라서 맨 위의 코드처럼 함수에 인자를 2개 더 쓰지 않고, promise 객체 자체를 리턴하도록 쓰면 된다. (2개의 콜백 함수는 원래부터 정해져 있기 때문에 개발자가 마음대로 이름을 바꿀 수 없다.) Promise는 이 두 개의 콜백 함수중 하나만 실행하기 때문에, state도 하나의 값만 가지게 된다. 즉, 처음으로 state가 정해지면 그 이후에 state를 변경하는 코드들은 모두 무시된다는 말이다.
아무튼 맨 위의 코드를 Promise를 이용하여 정리하면 다음과 같다.

function login(id, password){
 return new Promise(resolve, reject){
   if(id === "sue" && password === "1234"){
    resolve("hello, you have admin role");
  }else{
    reject(Error("404 not found"));
  }
 } 
}

여기까지는 Promise를 사용하여 함수를 선언하는 과정이었다. 그렇다면 실제 함수를 호출할 경우, Promise를 어떻게 사용하여야 하는가?

Promise 객체의 키 값 조작하기-then, catch, finally

mdn문서를 찾아보니 promise는 객체의 일종이고, state와 result 라는 2개의 키를 가진다.
각각의 키는 상태에 따라서 다음과 같은 값들을 가질 수 있다.
(실제 콘솔로그에는 아래와 같이 찍히지는 않음.)

Promise = {state: pending(또는 fulfilled, rejected), result: value(또는 error)}

각각의 값에 대해서 설명을 하자면 다음과 같다.
pending의 경우 이행하거나 거부되지 않은 초기 상태를 의미한다.
즉 Promise가 막 생성되었을때를 가리킨다.
fulfilled의 경우 어떤 작업이 성공적으로 수행되었을때의 상태를 의미한다.
rejected의 경우 어떤 작업이 실패하였을때의 상태를 의미한다

result 키는 2개의 값을 가지는데, 성공적으로 작업이 완료될 경우, resolve가 value를 반환하며, 실패할 경우 reject이 error객체를 사용하여 에러의 원인이나 관련 내용을 반환한다.

이 값들은 then, catch, finally로 조작이 가능하다.
작업이 성공일 경우 then, 실패할 경우 에러를 핸들링하기 위해 catch, 작업의 성공 유무와 상관없이 핸들링이 필요한 경우 finally를 사용하는데, 아래 코드와 같이 활용할 수 있다.

function login(id, password){
 return new Promise(resolve, reject){
   if(id === "sue" && password === "1234"){
    resolve("hello, you have admin role");
  }else{
    reject(Error("404 not found"));
  }
 } 
}

login("kxz","rjd782")//
.then(value => console.log(value))
.catch(error => console.log(error))
.finally(()=> console.log('final'))

실제로는 무슨 값이 인자로 전달되는지 이미 알고 있기 때문에, 아래와 같이 간단하게 적을 수 있다.

login("kxz","rjd782")//
.then(console.log)
.catch(console.log)
.finally(()=> console.log('final'))

Promise Chaining

아래의 코드는 콜백 함수가 또 다른 콜백함수를 불러오는 예시이다.
Promise는 연속적인 처리가 가능하기 때문에 이것도 간단하게 작성이 가능하다.
다만, 이것도 과도하게 사용하면 콜백 지옥과 같은 효과를 불러오기 때문에 차후 async, await를 이용하여 정리하는 것이 제일 좋다.

const hen = () => {
  return new Promise(resolve, reject){
    resolve('white hen')
  }
}

const egg = (hen) => {
  return new Promise(resolve, reject){
    resolve(`${hen} => white egg`)
  }
}

const cook = (egg) => {
  return new Promise(resolve, reject){
    resolve(`${egg} => sunny side up`)
  }
}

//promise chaining을 하면 다음과 같이 작성할 수 있다

hen()//
.then(value => egg(value))
.then(value => cook(value))
.then(value => console.log(value))


//어떤 값을 전달해야 하는지 알고 있기 때문에 더 간단히 쓸 수 있다.

hen()//
.then(egg)
.then(cook)
.then(console.log) //white hen => white egg => sunny side up 출력

만약에 위 세 함수 중 egg함수에서 에러가 난 경우에는,
(1)에러 그 자체를 출력하거나, (2) 에러를 핸들링하는 코드를 작성할 수 있다.

const hen = () => {
  new Promise(resolve, reject){
    resolve('white hen')
  }
}

const egg = (hen) => {
  new Promise(resolve, reject){
    reject(Error('404 not found!'))
  }
}

const cook = (egg) => {
  new Promise(resolve, reject){
    resolve(`${egg} => sunny side up`)
  }
}

//(1)에러 그 자체 출력

hen()//
.then(egg)
.then(cook)
.then(console.log) 
.catch(console.log) //에러내용 바로 출력


//(2)에러를 핸들링

hen()//
.then(egg)
.catch(error => 'bread')
.then(console.log) //white hen =>bread =>sunny side up
.catch(console.log) //에러내용 바로 출력.
profile
몫을 다하는 사람

0개의 댓글