BEB 07 4-2

Donghun Seol·2022년 10월 5일
0

코드스테이츠 BEB 07

목록 보기
14/39

자바스크립트와 비동기

자바스크립트는 비동기적 언어인가?

아니다. 자바스크립트는 싱글스레드 기반으로 동기적으로 동작하는 언어다. 명령어를 처리하는 콜스택이 하나고, 이 콜스택은 동기적으로 순차적으로 처리된다. 그럼에도 불구하고 자바스크립트에 내장된 백그라운드, 매크로 태스크큐, 마이크로 태스크 큐, 이벤트 루프는 비동기와 유사한 명령어 실행을 가능하게 해준다.

비동기의 동기적 실행

비동기 함수들을 사용자의 의도대로 동기적으로 실행시키기 위해 아래의 방법이 활용된다.

  1. 콜백함수
  2. 프로미즈 체이닝 (.then(), .catch(), .finally())
  3. async / await

아래에서는 커피를 비동기적으로 제조하는 함수를 다양한 형식으로 표현해 보겠다.
이 함수의 원형은 아래와 같다.

function makeCoffee(name, time) {
  setTimeout(() => {
    console.log(name);
  }, time);
}
makeCoffee('espresso', 500);
makeCoffee('milkShake', 3000);
makeCoffee('americano', 1000);
makeCoffee('mixCoffee', 0);

// mixCoffee, espresso, americano, milkShake 순으로 출력된다.

1. 콜백함수

콜백함수를 활용하는 방법은 직관적이지만 코드가 장황해지고, 에러 핸들링을 각각 해주어야 하므로 권장되지 않는다. 다만 콜백함수를 여러번 호출해야 되는 경우에는 콜백함수를 사용할 수 밖에 없다. promise를 활용하면 콜백함수를 한번밖에 호출하지 못한다.

makeCoffee(name, time)를 아래와 같이 수정하여 콜백을 넣어 호출하면 동기적으로 실행가능하다. 아름다움으로 널리 알려져 있는 콜백지옥을 확인 가능하다.

function makeCoffeeCb(name, time, callback) {
  setTimeout(() => {
    //console.log(name);
    callback(name)
  }, time);
}

makeCoffeeCb('espresso', 500, (name) => {
  console.log(name);
  makeCoffeeCb('milkShake', 3000, (name) => {
      console.log(name);
    makeCoffeeCb('americano', 1000, (name) => {
        console.log(name);
      makeCoffeeCb('mixCoffee', 0, (name) => {
          console.log(name);
      })
    })
  })
});

// espresso, milkShake, americano, mixCoffe

2. 프로미즈

ES2015 부터 콜백기반의 비동기함수 흐름제어의 단점을 보완하기 위해 추가된 문법이다. 현재는 대부분의 API들이 프로미즈를 활용해 재구성되어 있으며, 실제 자주 사용하므로 숙지해야 한다.
Promise란 실행은 바로 되지만 결괏값은 나중에 받기로(결정되기로) 약속되어 있는 객체다. 따라서 Promise가 생성되면 항상 pending 상태이며 resolve 또는 reject되어야 fulfilled 상태가 된다.
Promise는 thenable 객체이므로 .then .catch .finally 메서드를 체이닝하여 fulfilled 상태가 된 이후의 동작을 동기적으로 제어할 수 있다.

makeCoffe()를 promise를 반환하는 함수로 작성하면 아래와 같다.
비동기 함수 내에서 console.log로 name을 출력하는 것 보다 promise 체인으로 값을 전달하며 해당 체인 내에서 출력하는 것이 좀더 활용성이 높으므로 아래와 같이 작성하였다.

function makeCoffeePromise(name, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      //console.log(name);
      resolve(name);
    }, time)
  });
}

makeCoffeePromise('espresso', 500)
  .then((name) => {
    console.log(name);
    return makeCoffeePromise('milkShake', 3000);
  })
  .then((name) => {
    console.log(name);
    return makeCoffeePromise('americano', 1000);
  })
  .then((name) => {
    console.log(name);
    return makeCoffeePromise('mixCoffee', 0);
  });

3. async/await

async 선언된 함수의 반환값은 항상 promise가 된다. 만약 rejected Promise를 구현하고 싶으면 Error객체를 throw 해준다.

function makeCoffeePromise(name, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      //console.log(name);
      resolve(name);
    }, time)
  });
}
async function makeCoffees() {
  let espresso = await makeCoffeePromise('espresso', 500);
  let milkShake = await makeCoffeePromise('milkShake', 300);
  let americano = await makeCoffeePromise('americano', 1000);
  let mixCoffee = await makeCoffeePromise('mixCoffee', 0);

  console.log(espresso, milkShake, americano, mixCoffee);
}

makeCoffees();

Promise.all([arr of promises]); 병렬실행, 모든 promise가 성공해야 값을 반환하고, 그렇지 않으면 rejected
Promise.allSettled([arr of promises]); 병렬실행 resolve, reject 관계없이 모든 promise가 완료되기만 하면 resolve된다.
Promise.race 최초로 settled된 promise만 반환한다. rejected된 promise도 반환함을 유의해야 함.
Promise.any 최로로 resolve된 promise를 반환한다. rejecte된 promise는 건너뛴다.

for await (promise of promises) {
	console.log(promise);
}

macrotasks vs microtasks

이벤트루프가 백그라운드 큐에서 대기하고 있는 명령을 콜스택에 올릴때 우선순위는
마이크로 태스크 큐 > 매크로 태스크 큐
microtasks

  • promise
  • process.nextick()

macrotasks

  • setTimeout
  • setInterval
  • setImmediate

callback to promise refactoring


  function go() {
    showCircle(150, 150, 100, div => {
      div.classList.add('message-ball');
      div.append("안녕하세요!");
    });
  }

  function showCircle(cx, cy, radius, callback) {
    let div = document.createElement('div');
    div.style.width = 0;
    div.style.height = 0;
    div.style.left = cx + 'px';
    div.style.top = cy + 'px';
    div.className = 'circle';
    document.body.append(div);

    setTimeout(() => {
      div.style.width = radius * 2 + 'px';
      div.style.height = radius * 2 + 'px';

      div.addEventListener('transitionend', function handler() {
        div.removeEventListener('transitionend', handler);
        callback(div);
      });
    }, 0);
  }



// 외우기 쉽게 정리하면
// promise 형식으로 리팩터한 활용한 코드
// 리턴값을 new Promise객체로 선언하고, 비동기 로직을 promise객체의 콜백함수로 넣고,
// callback을 resolve로 바꿔준다.
function go() {
    showCircle(150, 150, 100).then(div => {
      div.classList.add('message-ball');
      div.append("Hello, world!");
    });
  }

function showCircle(cx, cy, radius) {
  let div = document.createElement('div');
  div.style.width = 0;
  div.style.height = 0;
  div.style.left = cx + 'px';
  div.style.top = cy + 'px';
  div.className = 'circle';
  document.body.append(div);

  return new Promise(resolve => {
    setTimeout(() => {
      div.style.width = radius * 2 + 'px';
      div.style.height = radius * 2 + 'px';

      div.addEventListener('transitionend', function handler() {
        div.removeEventListener('transitionend', handler);
        resolve(div);
      });
    }, 0);
  })
}

비동기 실습을 위해 작성해본 코드

const orders = [
  { name: "latte", time: 2000, },
  { name: "shake", time: 4000, },
  { name: "americano", time: 1000, },
  { name: "cookie", time: 0, },
];

// callback pattern

function processOrder(order) {
  setTimeout(() => {
    console.log(order.name);
    return order.name;
  }, order.time);
}

function processOrderPromise(order) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(order.name);
      resolve(order.name + 'is read);
    }, order.time);
  });
}

// processOrderPromise(orders[0])
//   .then(() => processOrderPromise(orders[1]))
//   .then(() => processOrderPromise(orders[2]))
//   .then(() => processOrderPromise(orders[3]));

async function processOrdersAsync(orders) {
  console.time('promise in order');
  let latte = await processOrderPromise(orders[0]);
  let shake = await processOrderPromise(orders[1]);
  let americano = await processOrderPromise(orders[2]);
  let cookie = await processOrderPromise(orders[3]);
  console.timeEnd('promise in order');

  console.time('promise All');
  let latteAll = processOrderPromise(orders[0]);
  let shakeAll = processOrderPromise(orders[1]);
  let americanoAll = processOrderPromise(orders[2]);
  let cookieAll = processOrderPromise(orders[3]);
  await Promise.all([latteAll, shakeAll, americanoAll, cookieAll]);
  console.timeEnd('promise All');
}

processOrdersAsync(orders);
profile
I'm going from failure to failure without losing enthusiasm

0개의 댓글