본문 바로가기

[JS] 비동기 실습하기

[JS] 비동기 실습하기

자바스크립트는 비동기를 왜 고민하게 되었나?

왼쪽 (동기를 연출한 상황)                        vs                            오른쪽 (비동기)

 

1.  컴퓨터의 유휴 자원을 활용하기 위함 

2. 비동기가 필요한 것은 맞다. 하지만 아래 예제에서 영상 재생 & 멈춤, 제목 표시 & 강조 & 초기화 등이 동시다발적으로 일어나게 되면 순식간에 지나가기 때문에 (작업은 시작부터 완료되었지만 우리 눈에는 마치 멈춘 것과도 같다.) 즉, 의도적 지연시간이 필요하다.

이렇게 적당히 간격을 두고 프로세스가 진행되어야 한다.

 


 

비동기 구현하기

가장 간단한 비동기 구현은 타이머 API를 이용하는 방법이다. 타이머 API는 setTimeout()이라는 메서드를 이용해 구현할 수 있다. callback과 promise, 그리고 async/await 키워드를 이용한 비동기 흐름을 제어하는 방법을 코드를 통해 비교해보자.

 

1. callback

 delay 함수

const delay = (wait, callback) => {     		// 콜백 형식
  setTimeout(callback, wait);
}
callback 
// 1. callback --------------------------------------------------

function runCallback() {       
  resetTitle();
  playVideo();

  delay(1000, () => {           // 1초 뒤 작동
    pauseVideo();
    displayTitle();

    delay(500, () => {         // 0.5초 뒤 작동
      highlightTitle();

      delay(2000, resetTitle); // 2초 뒤 작동
    });
  });
}

 

2. Promise

기본 형태
 sleep 함수
const sleep = (wait) => {				// Promise 형식
  return new Promise((resolve) => {
    setTimeout(resolve, wait);
  });
}

 

Promise
2. Promise ---------------------------------------------------

function runPromise() {         
  resetTitle();
  playVideo();

  sleep(1000).then(() => {
    pauseVideo();
    displayTitle();
  })
    .then(sleep.bind(null, 500))          // 0.5초 뒤에  * (설명1)
    //.then(() => {return sleep(3000);}) 라고도 쓸 수 있다.
    .then(highlightTitle)            
    //.then(()=>highlightTitle())        // 이 표현이 생략된 것
    
     
    .then(sleep.bind(null, 2000))        // 2초 뒤에 
    .then(resetTitle)		         // 제목이 사라짐
}

 

(설명1) bind를 써 줘야 하는 이유

 이 Promise 인스턴스는 sleep 함수의 인자로 전달받은 wait 만큼의 시간(초)을 기다린 후 resolve 함수를 실행한다. 이 때, resolve 함수에는 어떤 인자도 전달되지 않는다. 따라서 Promise 인스턴스의 resolved 결과값 undefined 가 된다.

 

then() 메소드의 인자로 sleep 함수의 호출값이 전달되었을 경우에도 sleep 함수가 반환하는 Promise 인스턴스의 resolved 결과값 undefined 이기 때문에, 다음 Promise Chain 으로 전달되는 값 역시 undefined 가 된다.

 

시간 지연을 발생시키는 Promise 인스턴스가 아닌 undefined 값이 그 다음 Promise Chain 에 전달되었기 때문에, Promise Chain  시간 지연 없이 전달된 값(undefined)을 곧바로 실행한다.

 

function.bind(thisValue); // this 키워드만 전달
function.bind(thisValue, arg1, arg2, ... argN); // this 키워드 & 함수의 인자 N개 전달
function.bind(null, arg); // this 키워드는 전역 객체, 함수 인자는 arg

 

bind() 메소드는 바인드된 함수와 동일한 작업을 하는 새로운 함수를 생성하여 반환한다.

A bound function may also be constructed using the new operator if its target function is constructable. Doing so acts as though the target function had instead been constructed. The prepended arguments are provided to the target function as usual, while the provided this value is ignored (because construction prepares its own this, as seen by the parameters of Reflect.construct). If the bound function is directly constructed, new.target will be the target function instead. (That is, the bound function is transparent to new.target.)

 

class Base {
  constructor(...args) {
    console.log(new.target === Base);
    console.log(args);
  }
}

const BoundBase = Base.bind(null, 1, 2);

new BoundBase(3, 4); // true, [1, 2, 3, 4]

 

참고 자료

예시에서 첫 번째 .then은 1을 출력하고 new Promise(…)를 반환((*))한다.
1초 후 이 프라미스가 이행되고 그 결과(resolve의 인수인 result * 2)는 두 번째 .then으로 전달된다. 두 번째 핸들러((**))는 2를 출력하고 동일한 과정이 반복된다.

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) { // (**)

  alert(result); // 2

  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) {

  alert(result); // 4

});

 

 

 


 

sleep 함수를 아래와 같이 수정하고,

// resolve 에 'hello'를 담아서 then의 인자로 전달한다.
const sleep = (wait) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('hello');
    }, wait);
  });
};
// 에러가 났을 경우 
const sleep = (wait) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('에러'));
    }, wait);
  });
};

 

runPromise 함수를 다음과 같이 수정해보고 결과를 비교해본다.

function runPromise() {
  resetTitle();
  playVideo();

  sleep(1000)
    .then((param) => {
      console.log(param);
      pauseVideo();
      displayTitle();
      return "world";
    })
    .then((param) => {
      console.log(param);
      return sleep(5000);
    })
    .then(highlightTitle)
    .then(sleep.bind(null, 2000))
    .then(resetTitle);
}
 .catch(err => {
      console.log(err);
    })

 


3.  async/await

 sleep 함수
const sleep = (wait) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("hello");
    }, wait);
  });
};
async/await
async function runAsync() {  
                            
  resetTitle();
  playVideo();

  await sleep(1000);     // 1초 뒤
  pauseVideo();
  displayTitle();

  await sleep(500);      // 0.5초 뒤
  highlightTitle();

  await sleep(2000);     // 2초 뒤 
  resetTitle();
}

// 비동기 함수가 마치 동기 함수처럼 작동한다. (실제로는 Promise를 이용하여 결과를 리턴)

 

브라우저 개발자 도구의 콘솔을 열어 다음을 실행해 본 후, returnValue에 담긴 값은 'hello' 가 나온다. (resolve의 value 값)

 

let returnValue = await sleep(1000);

 

  • async/await 키워드와 함께 실행되는 함수는 promise 다.
  • await 키워드 다음에 등장하는 sleep 함수 실행은 promise를 리턴하고 있다.
async function fn() {
 
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });
 
  let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
}
 
fn();
 
728x90
⬆︎