Asynchronous(비동기), Promise

Asynchronous

JavaScript환경에서 구동되는 Node.js는 다음과 같은 특성을 가진다.

a single-threaded
non-blocking
asynchronous
concurrent

이 때 비동기(asynchronous)란,
클라이언트(서버로 접속하는 컴퓨터(나))와 서버의 관계를 가정할 때,
클라이언트는 서버로 특정 요청을 보내고나서 이후 응답을 기다리지 않고 다른 작업을 계속한다.
이 후 서버로부터 응답에 대한 결과가 회신되면 해당 작업을 완료 후 또 다른 작업을 수행한다.
예를 들어, Youtube 시청 시, 동영상이 로딩되는 동안 댓글 확인, 타 동영상 목록 확인, 홈 이동 버튼 등의 기능이 작동하는 것은 모두 비동기적인 특성이라고 볼 수 있다.

클라이언트에서 각 작업에 대한 요청을 우선 다 보내고, 서버에서 응답이 왔을 때 종료한다.

비동기적으로 실행되는 코드의 동작 순서를 제어하기 위한 방안

비동기 특성을 여러 task에 사용할 경우, 단순하게 병렬구조로 기능이 나열되어 어느 task가 먼저 수행 혹은 완료될지 알 수 없어 이후 시스템 응답을 예측하는데 어려움이 생긴다. (task마다 필요 시간이 다르기 때문)

1. Callback

가장 간단한 방법이다. 비동기 함수 끼리 엮어 리턴문에 다음 함수가 실행되도록 callback문을 작성한다. (계층구조) 그러나, callback항목이 많아지면 가독성이 현저하게 떨어질 수 있다. (hell..)

2. Promise & then

이러한 문제점을 개선하기 위해 Promise가 사용된다.
Promise는 비동기 실행이 끝나면, 성공(resolve)/실패(reject)에 따라 입력받은 함수를 실행해줄게!라고 Promise해준다.
성공한 경우 .then()으로 다음 함수를 실행시키고, 실패한 경우 .catch()로 실패시의 핸들링 함수를 실행시킬 수 있다.

Callback chain에서 .catch()를 사용하여 각 .then()단계에서 발생한 에러를 마지막 단계에서 한 번에 취합이 가능함

Callback chain을 다루기 위한 방안으로서 class처럼 사용한다.

function eatLunch() {
    return new Promise((resolve, reject) => {
      // Promise가 요구하는 두 전달 인자
        setTimeout(() => { resolve('1. eat lunch') }, Math.floor(Math.random() * 100) + 1)
    })
}
 
function goToBed() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('2. goToBed') }, Math.floor(Math.random() * 100) + 1)
    })
}
 
eatLunch()
.then(data => {
    console.log(data)
    return goToBed() // return문을 통해 가독성을 높일 수 있다.
})
.then(data => {
    console.log(data)
})

async & await (1)

promise를 보다 쉽게 사용할 수 있는 새로운 await 문법을 통해 다른 방식의 비동기 흐름제어가 가능하다.

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)
    })
}

const result = async () => {
    const three = await eatLunch();
    console.log(three)

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

result();

async & await (2)

위 await의 또 다른 예제 및 풀이를 아래에 기술한다.

function myfunc(text, err) {
    console.log("input :", text);
    return new Promise(function(resolve, reject) {
      if(!err) {
        resolve();
      }
      else {
        let ms = ["err1", "err1"];`
        reject(ms);
      }
    });
  }
  // 함수는 변수를 할당, 저장할 수 있는 일급 객체이므로
  //const test = myfunc("Hello!");
  //test.then(function() {}) 대신 아래처럼 생략 가능
  myfunc("Hello!", false)
  .then(function() {
    console.log("hi~");
  //return myfunc2()
//여러 함수을 사용하여 then chain을 구성하려면 return문에 다른 함수를 입력해야 함

// 그러나 이렇게 then chain을 구성하면 catch를 통해 걸러낸 에러가 어느 함수에서 발생한지 알 수가 없음
// 그럴 때 리턴을 없애고 함수실행만 해준뒤 그 아래에 catch를 써주기도 한다.
  })

// input : Hello! 첫번째 콘솔로그 싫행

// hi~  err가 false이므로(!err, err가 아니므로, if문에 의해 resolve() 실행이 되면 then을 실행  

// err가 false가 아닌 true일 때, reject()가 실행되며 이를 위해 catch를 사용한다.
  .catch(function(err_ms) {
      console.log(err_ms[0], err_ms[1]);
  });

  //catch에 인자가 2개 이상이 들어갈 경우 인자 하나에 정의해준 뒤 전달해줘야 함
  //이러한 문제점을 해결하고 가독성이 좋은 코드를 가능할 수 있다.

  const run = async function() { //promise 객체를 실행하기 위해 await 사용
    try{
      let task1 = await myfunc("await사용!!!", false); // await에 의해 catch가 실행됨
     // Promise 이외에 작성된 로직은 해당 구문을 통해 실행이 되고
     // 추가적으로 resolve()에 작성된 로직은 await 아래에 작성한다.
//즉 기존의 then영역에 작성하는 코드는 await아래에 작성한다.

      console.log("myfunc함수의 resolve()의 인자가 들어감");
      let task2 = await myfunc2("또다른 await!!", false);
      console.log("myfunc2함수의 resolve()의 인자가 들어감");
    }catch(er){

        console.log(e);  // try & catch를 통해 각 단계에서 에러를 검출할 수 있다.
    } 
  }
  run();

Promise.all

promise.all()은 두 함수를 붙일 때 유용하게 사용될 수 있다.
아래의 사용 예를 나타낸다.

Promise.all([func1("text1", false), func2("text2", false)])
// Promise.all의 인자는 배열의 형태
// 인자는 new Promise가 들어가야하지만 func함수는 new Promise를 리턴하므로 그 자체라고 볼 수 있음
.then(function([factor1, factor2]) {
    console.log(factor1, factor2);// func1 & func2 모두의 then
})
.catch(function(err) { 
  // 각 단계에서 하나라도 에러가 터지면 에러를 표시하고 다음 단계를 실행하지 않음
    console.log(err);
})

node.js 내장모듈 사용법

브라우저에서 script를 사용하는 것과 달리

<script src="불러오고싶은_스크립트.js"></script>

node.js에서는 코드 상단에 require을 사용한다

const dns = require('dns') // DNS 모듈을 불러옵니다

외부 모듈 사용법

$ npm install 외부모듈

Event Loop

thread는 동시에 실행할 수 있는 단위 //single thread, multi-thread
process는 작업하는 사람 수
1. call stack에서 작업1,2,3을 실행
2. 작업(메소드)1,2,3을 모두 background로 보내서 실행
3. 작업 완료 순서대로 task que에 push
4. 같은 순서대로 call stack으로 복귀
5. Event loop 종료

좋은 웹페이지 즐겨찾기