오늘 배운 것

  • Asynchoronous Javascript
  • Callback
  • Promise
  • Async & Await

intro

비동기를 설명하는 사진

Asynchoronous Javascript

왜 비동기여야 하는가

1. 동기적 처리의 치명적인 문제


동기적으로 처리하게 되면 서버의 응답이 오기 전까지 클라이언트는 대기 상태에 있다가, 서버가 응답을 하면 나머지 연산을 처리하게 되는데, intro 짤에서 본 것과 같이 컵라면이 다 익기 전에 다른 컵라면에 물을 부을 수 없다는 얘기와 같다.
유튜브를 보는데 영상이 로드되고 재생이 끝나기 전까지 다른 영상을 찾을 수도 없고 댓글을 볼 수 없다면 그 얼마나 참담한 상황인가

2. 시간 절약의 문제


일을 할 때 앞 사람이 일을 끝내기를 기다렸다가 끝난 뒤 다음 사람이 일을 시작하는 방법으로 작업을 한다면 시간은 무지막지하게 오래 걸릴 것이다.
컴퓨터에서 뭔가를 전송하거나 할 때도 순차적으로 하나하나 하다보면 시간이 굉장나게 길어진다.

그래서 비동기적으로(동시에 막 이것저것) 처리하면 소요되는 시간 면에서 상당한 절약을 할 수 있다.

Callback

비동기적으로 연산을 처리하는 건 좋지만, 각자 처리에 걸리는 소요시간이 다르기 때문에 결과값이 뒤죽박죽이 되어 버릴 수도 있다.

	
const printString = (string) => {
    setTimeout(
      () => {
        console.log(string)
      }, 
      Math.floor(Math.random() * 100) + 1
    )
  }
 
  const printAll = () => {
    printString("A")
    printString("B")
    printString("C")
  }
  printAll()

이런 상황이라면 콘솔에 찍히는 순서가 매번 다를 것이다.
근데 이렇게 뒤죽박죽이라면.. 예상치 못한 결과가 항상 나오게 되기 때문에 순서는 제어를 해 줄 필요가 있다.
이 때 콜백을 사용하면 제어가 가능하다.

	
const printString = (string, callback) => {
    setTimeout(
      () => {
        console.log(string)
        callback()
      }, 
      Math.floor(Math.random() * 100) + 1
    )
  }
 
  const printAll = () => {
    printString("A", () => {
      printString("B", () => {
        printString("C", () => {})
      })
    })
  }
  printAll()

이렇게 콜백을 넣으면 콘솔에 순차적으로 찍히도록 만들 수 있다.

근데 이러면 비동기가 아니라 동기적으로 처리하는거 아닌가요?
=> 아니다. 저게 단순히 콘솔에 찍어주는거라서 동기적으로 된다고 생각할 수 있지만, 만약 어떠한 작업을 하는 코드나 문서였다면 미리 작업을 종료한 뒤 행동만 순차적으로 하게 되는 것이다.

callback error handling design

콜백을 이용하여 오류를 핸들링할 수도 있다.

const somethingGonnaHappen = callback => {
    waitingUntilSomethingHappens()
 
    if (isSomethingGood) {
        callback(null, something)
    }
 
    if (isSomethingBad) {
        callback(something, null)
    }
}

// usage
somethingGonnaHappen((err, data) => {
    if (err) {
        console.log('ERR!!');
        return;
    }
    return data;
})

callback hell

콜백은 이와 같이 여러 방법으로 활용할 수 있다.
그러나 역시 장점이 있으면 단점도 있는 법... 콜백을 여러번 중첩하여 사용하게 되면 이런 상황이 벌어진다.

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
가독성이 저세상을 가버린다

그래서 우리는 약속(promise)을 하나 한다..

Promise

설명

프로미스는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용한다.

일반적으로 웹 어플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위해 api를 사용하는데,
api가 실행되면 서버에 데이터를 요청한다.
그러나, 데이터를 다 받아오기도 전인데도 데이터를 모두 받아 온 것처럼 화면에 데이터를 표시하려고 하면 오류가 발생하거나 빈 화면이 뜬다. 이와 같은 문제를 해결하기 위한 방법 중 하나가 프로미스이다.

프로미스의 3가지 상태

new Promise()로 프로미스를 생성하고 종료될 때 까지 프로미스는 3가지 상태를 갖는다.

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

활용법

아까 우리가 콜백으로 에러 핸들링을 했던 것처럼, promise라는 인스턴스를 이용해서 처리할 수 있다.

위에서 만들었던 콜백 지옥을 promise를 이용하면 아래와 같이 표현할 수 있는데,

const printString = (string) => {
    return new Promise((resolve, reject) => {
      setTimeout(
        () => {
         console.log(string)
         resolve()
        }, 
       Math.floor(Math.random() * 100) + 1
      )
    })
  }

  const printAll = () => {
    printString("A")
    .then(() => {
      return printString("B")
    })
    .then(() => {
      return printString("C")
    })
  }
  printAll()

resolvereject, 그리고 .then()이 새로 생긴 것을 볼 수 있다.
resolvereject는 위 그림처럼 핸들링 선택지라고 보면 될 것이고,
.then()은 앞서 작업이 정상적으로 종료되었을 때 이 작업을 실행하라는 설명문이라고 생각하면 쉽다.

프로미스에는 .catch()도 있는데, 아래와 같이 생각하면 된다.

A가 성공적으로 끝났다. 그때(.then()) 이걸 실행해줘.
A가 실패했다. 그럼 잡았다 요놈!! (.catch()) failure task를 실행해줘.

주의사항

Promise도 hell이 발생할 수 있다

function gotoCodestates() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('1. go to codestates') }, Math.floor(Math.random() * 100) + 1)
    })
}

function sitAndCode() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('2. sit and code') }, Math.floor(Math.random() * 100) + 1)
    })
}

function eatLunch() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('3. eat lunch') }, Math.floor(Math.random() * 100) + 1)
    })
}

function goToBed() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('4. goToBed') }, Math.floor(Math.random() * 100) + 1)
    })
}

gotoCodestates()
.then(data => {
    console.log(data)

    sitAndCode()
    .then(data => {
        console.log(data)

        eatLunch()
        .then(data => {
            console.log(data)
            
            goToBed()
            .then(data => {
                console.log(data)
        
            })
        })
    })
})

이 얼마나 보기 싫은가
Promise hell을 보기 싫다면 return을 적재적소에 잘 이용하면 된다

gotoCodestates()
.then(data => {
    console.log(data)
    return sitAndCode()
})
.then(data => {
    console.log(data)
    return eatLunch()
})
.then(data => {
    console.log(data)
    return goToBed()
})
.then(data => {
    console.log(data)
})

위와 같이 return을 이용하여 promise를 이어주는 것을 Promise Chaining이라고 부른다.

Async & Await

async와 await는 비교적 최근 추가된 키워드이다.
이 키워드들을 이용하여 비동기 코드들을 동기 코드처럼 순차적으로 진행하도록 할 수 있다.

async

async는 항상 function 키워드 앞에 위치한다.
async function은 항상 promise를 반환한다.
promise가 아닌 값을 반환하더라도 이행 상태의 promise로 값을 감싸서, 이행된 promise가 반환되도록 한다.

async function f() {
  return Promise.resolve(1);
}

이렇게 명시적으로 프로미스를 리턴해도 되고

async function f() {
return 1;
}

이렇게 다른거 다 생략하고 value만 리턴해도 자바스크립트는 프로미스로 인식한다.

await

awaitasync 안에서만 작동한다.
자바스크립트는 await을 만나면 promise가 처리(settled)될 때까지 대기하고, 결과는 그 이후 반환된다.

주의 : async 함수가 아닌데 await을 사용하면 문법 오류가 발생한다

profile
하루하루 배울때마다 기록하는 일기장

0개의 댓글