비동기 Async

2021. 9. 22. 17:40
반응형

1. 비동기

1) blocking vs non-blocking
- blocking : ex) 전화. 하던 일을 멈추고 받아야 한다. 요청에 대한 결과가 동시에 일어난다. (synchronous)
- non-blocking : ex) 문자. 확인 후 나중에 답장 할 수 있다. 요청에 대한 결과가 동시에 일어나지 않는다. (asynchronous)
2) 커피 주문으로 알아보는 동기 vs 비동기
- 동기 : 요청에 대한 결과가 동시에 일어난다. / 비동기 : 요청에 대한 결과가 동시에 일어나지 않는다.

2. 비동기 Javascript

클라이언트가 요청을 해놓고 다른 작업을 하다가 응답을 받고,
그 응답을 받은 것을 실행시킬 수 있는 비동기적인 작업이 좋다는 것은 알겠는데, 비동기라 함은 예상할 수 없다는 것과 같은 의미이다.
비동기로 어떤 task들을 순차적으로 실행을 했을 때, 순차적으로 결과가 나오지는 않을 것이다.
왜냐하면 task마다 걸리는 시간이 다르기 때문이다.
그래서 순서를 제어하고 싶은 경우엔 어떻게 할까?

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

const printAll = () => {
  printString("A")
  printString("B")
  printString("C")
}
printAll() //A B C 순서대로 찍히지 않는다.

1) callback
- "그 일 다 끝나면 콜백 넘겨주는거 실행해줘" 같은 느낌..

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

const printAll = () => {
  printString("A", () => {
    printString("B", () => {
      printString("C", () => {})
    })
  })
}
printAll() //A B C 순서대로 찍힌다.

- Callback error handleing Design(에러 핸들링)

const somethingGonnaHappen = callback => {
  waitingUntilSomethingHappens() // 기다린 이후

  if(isSomethingGood) { // 잘 됐으면
    callback(null, something) // 콜백함수 앞의 인자에 null, 뒤에 something을 넣어준다.
  }
  if(isSomethingBad) { // 에러가 나거나 원하는 결과가 이루어지지 않았을 때
    callback(err, null) // 앞의 인자에 err같은 것을 넣어주고, 뒤에는 null을 넣어준다.
  }
}

- Usage(사용법)

somethingGonnaHappen((err, data) => {
  if(err) { //에러가 있으면
    console.log('ERR!!'); //콜솔창에 ERR!!라고 띄워줘
    return;
  }
  return data; //에러없으면 그냥 data가 리턴된다.
})

- callback is great but there is one thing, callback HELL

callback HELL

callback이 순차적으로 이루어 져서 좋긴한데, 코드를 읽는 가독성이 너무 어렵고 코드를 관리하기가 어려워진다.
2) Promise
- 하나의 Class같은 거라서 new Promise()를 통해 하나의 instance가 만들어진다.
4) Promise.all
- 동시에 두 개 이상의 Promise 요청을 한꺼번에 실행하는 함수이다.

function gotoSchool() {
  return new Promise((resolve, reject) => {  //새로운 Promise instance를 리턴하는데 프로미스만의 콜백을 받는다.
    setTimeout(() => { resolve('1. go to school') }, 1000)
  })
}

function sitAndStudy() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('2. sit and study') }, 1000)
  })
}

function eatLunch() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('3. eat lunch') }, 1000)
  })
}

function goToBed() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('4. go to bed') }, 2000)
  })
}

const result = () => {
  gotoSchool()
  .then((value) => { //.then으로 실행을 이어나갈 수 있다.
    console.log(value)
    return sitAndStudy()
  })
  .then((v) => {
    console.log(v)
    return eatLunch()
  })
  .then((v) => {
    console.log(v)
    return goToBed()
  })
  .then((v) => {
    console.log(v)
    return;
  });
}
result();

이렇게 .then과 return을 활용해서 Promise chaining으로 callback hell, Promise hell 문제를 해결할 수 있다.
hell에서 벗어날 수는 있게 됐는데, 그래도 연결되는 함수가 많아지게 되면 복잡해지는 것은 마찬가지일 것이다.
그래서 다음으로 Async await에 대해서 알아보자.
3) Async await
- Promise인데 보이는 것이 좀 다르다.

function gotoSchool() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('1. go to school') }, 1000)
  })
}

function sitAndStudy() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('2. sit and study') }, 800)
  })
}

function eatLunch() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('3. eat lunch') }, 600)
  })
}

function goToBed() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('4. go to bed') }, 400)
  })
}

const result = async () => {
  const one = await gotoSchool();
  console.log(one)

  const two = await sitAndStudy();
  console.log(two)

  const three = await eatLunch();
  console.log(three)

  const four = await goToBed();
  console.log(four)
}

result();
//(1초 뒤) 1. go to school
//(one 나오고 0.8초 뒤) 2. sit and study
//(two 나오고 0.6초 뒤) 3. eat lunch
//(three 나오고 0.4초 뒤) 4. go to bed

await라는 것으로 비동기 함수들을 마치 동기적인 프로그램인것 처럼 쓸 수 있다.
그런데 await을 쓸때는 async함수라는 것을 반드시 표현해 주어야 사용할 수 있다.
goToBed가 실행하는데 걸리는 시간이 0.4초로 가장 짧아서 가장 먼저 실행될 것 같지만, await으로 인해 앞에있는 것이 모두 실행 된 뒤에야 실행한다.
그리고 이 async await으로 Promise함수를 일반함수 처럼 사용할 수 있다.
근데 이렇게 앞에 함수 다 기다렸다가 실행하게하면 이게 무슨 비동기인가 싶을 것이다.
위의 예제로 예를 들자면, one이 1초 걸리는 동안에 two, three, four은 이미 실행이 끝나있을 시간이다.
그런데 one이 끝나고 two를 시작하고, two가 끝나고 three를 시작하고, three가 끝나고 four를 실행하는 것은 분명 비동기가 아니다.
위의 예제에서 콘솔에 모든것이 다 뜨려면 총 2.8초가 걸린다.
근데 비동기적으로 실행시켰다고 했을 때 1초가 걸려야 비동기적으로 실행이 되었다고 할 수 있을 것이다.
그래서 아래 처럼 실행시키면 1초만에 모든 것이 실행된다.

function gotoSchool() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('1. go to school') }, 1000)
  })
}

function sitAndStudy() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('2. sit and study') }, 800)
  })
}

function eatLunch() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('3. eat lunch') }, 600)
  })
}

function goToBed() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('4. go to bed') }, 400)
  })
}

const result = async () => {
  const onePromise = gotoSchool();
  const twoPromise = sitAndStudy();
  const threePromise = eatLunch();
  const fourPromise = goToBed();

  const one = await onePromise;
  console.log(one)

  const two = await twoPromise;
  console.log(two)

  const three = await threePromise;
  console.log(three)

  const four = await fourPromise;
  console.log(four)
}

위 처럼 작성해주면 왜 1초만에 다 뜨게되는 것일까?
지금 onePromise, twoPromise, threePromise, fourPromise에는 pending 상태의 Promise객체가 담기게 된다.
그리고 await onePromise를 해주면 gotoSchool 함수가 실행되는데 걸리는 시간인 1초 뒤에 PromiseResult가 one에 담기면서 콘솔에 나타난다.
그리고 two, three, four Promise들은 one이 실행되는 동안 모두 fulfilled 상태가 되었기 때문에 바로 콘솔에 뜨게 되는 것이다.
* Promise에는 3가지 상태가 있다.

  • pending : 함수가 실행되고있는 상태
  • fulfilled : 함수 실행이 완료된 상태
  • rejected : 함수가 잘못 실행이 되었거나 에러가난 상태

callback이 순차적으로 이루어 져서 좋긴한데, 코드를 읽는 가독성이 너무 어렵고 코드를 관리하기가 어려워진다.
2) Promise
- 하나의 Class같은 거라서 new Promise()를 통해 하나의 instance가 만들어진다.
4) Promise.all
- 동시에 두 개 이상의 Promise 요청을 한꺼번에 실행하는 함수이다.

function gotoSchool() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('1. go to school') }, 1000)
  })
}

function sitAndStudy() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('2. sit and study') }, 800)
  })
}

function eatLunch() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('3. eat lunch') }, 600)
  })
}

function goToBed() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('4. go to bed') }, 400)
  })
}

const result = () => {
  Promise.all([gotoSchool(), sitAndStudy(), eatLunch(), goToBed()])
  .then(value => value.map(el => console.log(el)))
}

result();

이렇게 작성해주면 위의 async await으로 작성해준 것과 같다.
근데 조금 다른점은 async await은 한개 한개씩 처리가 되는데, Promise.all은 한꺼번에 처리가 된다는 점이다.
다시 말하자면, 위에서 함수가 걸리는 시간을 각각 1초, 1.2초, 1.4초, 1.6초가 걸리게 만들어놓았다고 가정을 했을 때,
async await에 작성한것 처럼 실행을 하면, 1초뒤에 one이 뜨고, 그 다음 0.2초 뒤에 two가 뜨고, 그 다음 0.2초 뒤에 three가 뜨고 이런식으로 하나씩 뜨게 되는데,
Promise.all은 하나씩 뜨는게 아니라 그냥 1.6초 뒤에 한번에 다 뜨게 된다.

이렇게 비동기에 대해서 알아보았다. 이것을 바탕으로 비동기를 다루는 상황을 많이 만나보면서 조금씩 더 다져나가야 할 것 같다.

반응형
LIST