자바스크립트 JS - Asynchronous & Promise

hyunnu·2021년 5월 26일
0
post-thumbnail

📖Review(21.05.26)

비동기 처리

자바스크립트의 비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성을 의미합니다.

Callback

setTimeout은 비동기 처리를 구현할 수 있는 함수이다.

//Asynchronous(비동기식) setTimeOut
console.log(`1`);
setTimeout(() => console.log(`2`), 1000);
console.log(`3`);

// Synchronous(동기식) callback
function printImmediately(print) {
  print();
}
printImmediately(() => console.log(`hello`));

// Asynchronous(비동기식) callback
function printWithDelay(print, timeout) {
  setTimeout(print, timeout);
}

printWithDelay(() => console.log(`async callback`), 2000);

callback hell

콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제이다.

$.get('url', function(response) {
	parseValue(response, function(id) {
		auth(id, function(result) {
			display(result, function(text) {
				console.log(text);
			});
		});
	});
});

콜백 지옥을 해결하는 방법

일반적으로 Promise, Async를 사용하는 방법이 있다. 만약 코딩 패턴으로만 콜백 지옥을 해결하려면 아래와 같이 각 콜백함수를 분리해주면 된다.

function parseValueDone(id) {
	auth(id, authDone);
}
function authDone(result) {
	display(result, displayDone);
}
function displayDone(text) {
	console.log(text);
}
$.get('url', function(response) {
	parseValue(response, parseValueDone);
});

Promise

  • promise는 JS에서 제공하는 비동기 코드를 간편하게 처리할 수 있도록 도와주는 object이다.
  • promise는 어떤 기능을 실행하고 나서 정상적으로 동작하면, 성공의 메세지와 함께 처리된 결과값을 전달해준다. 그러나 기능을 수행하지 못한다면 error를 전달해 준다.
  • State : pending(보류) → fulfilled(이행) → rejected(거부)
  • Producer vs Consumer

1. Producer (생산자)

  • 새 Promise가 생성되면 executor라는 콜백함수가 자동으로 실행됩니다.
const promise = new Promise((resolve, reject) => {
  // 시간이 오래 걸리는 무거운 처리 (network, read files ..)
  console.log(`doing something...`);
  setTimeout(() => {
    resolve(`david`); // 성공 하면
    // reject(new Error(`no network`)); // 실패 하면
  }, 2000);
});

2. Consumer (소비자)

  • then, catch, finally
promise
  .then((value) => {
    console.log(value);
  })
  .catch(error => {
    console.log(error);
  })
  .finally(() => { // 성공하든 실패하든 마지막에 실행됨
    console.log(`finally`);
  })

3. Promise chaining (연결하기)

  • then은 값을 바로 전달 할 수 도 있고, 리턴으로 Promise를 전달해도 된다.
const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then(num => {
    return num * 2; // > 2
  })
  .then(num => num * 3) // > 6
  .then(num => { // > 5
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000);
    });
  })
  .then(num => console.log(num)); // > 5

4. Error handlling

  • catch로 error를 핸들링 할 수 있다.
  • error가 발생해도 catch를 이용하면 전체적인 Promise 체인에 문제가 발생하지 않도록 빵꾸처리를 할 수 있다.
const getHen = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`🐓`), 1000);
    // setTimeout(() => reject(new Error(`error! 🐓`)), 1000);
  });
};
const getEgg = hen =>
  new Promise((resolve, reject) => {
    // setTimeout(() => resolve(`${hen} => 🥚`), 1000);
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
const cook = (egg) => 
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍗`), 1000);
    // setTimeout(() => reject(new Error(`error! ${egg} => 🍗`)), 1000);
  });

getHen()
  .catch(error => {
    console.log(error);
    return `💀`; // 빵꾸처리
  })
  .then(hen => getEgg(hen))
  .catch(error => {
    console.log(error);
    return `💀`; // 빵꾸처리
  })
  .then(egg => cook(egg))
  .catch(error => {
    console.log(error);
    return `💀`; // 빵꾸처리
  })
  .then(result => console.log(result))
  .catch(error => {
    console.log(error);
  })

// 정직한 코드
// getHen()
//   .then((hen) => {
//     return getEgg(hen);
//   })
//   .then((egg) => {
//     return cook(egg);
//   })
//   .then((result) => {
//     console.log(result);
//   })

// 파라미터가 1개일때 함수이름만 쓰면, 암묵적으로 함수의 매개변수로 전달 됨.
// getHen()
//   .then(getEgg)
//   .then(cook)
//   .then(console.log);

Async / Await

  • 프로미스 체이닝을 계속 하다보면 코드의 가독성이 떨어짐
  • async 와 await는 Promise를 간결/간편하고 동기적으로 실행되는것 처럼 보이게 만들어주는 API
  • async 와 await는 새로운 것이 추가 된게 아니라, 기존에 존재하는 Promise 위에 조금 더 간편한 API를 제공함 이런 것을 syntactic sugar 라고 한다 (Class도 마찬가지..)

비동기 처리를 반드시 해야하는 이유

  • JS 엔진은 블록({})안에 코드를 동기적으로 실행(처리) 함
  • 시간이 오래 걸리는 코드를 비동기 처리를 전혀 하지 않으면, 다음 코드에 문제가 발생할 수 있음
  • 예를 들어 서버에서 data를 받아와서 웹페이지에 출력하는 시나리오가 있다
  • data를 받아 오는데 10초가 걸림, 근데 비동기 처리를 안하면 텅 빈 data를 출력 해버림
  • Promise 상황극: "내가 언제 유저의 data를 받아 올진 모르겠지만 내가 약속할께, Promise라는 object를 가지고 있으면, 여기에 니가 then 이라는 콜백함수만 등록해 놓으면, 유저의 data가 준비 되는대로 니가 등록한 콜백함수를 불러줄께"

1. async

1.1 기존 방식(Promise)

// 프로미스를 사용하면 반드시 resolve와 reject를 호출해야 함
function fetchUser() {
  return new Promise((resolve, reject) => {
    // return `david`; // 프로미스 pending 상태
	resolve(`david`); // 프로미스 fulfilled 상태
	// reject(new Error(`error`)); // 프로미스 rejected 상태
  });
}

const user = fetchUser();
// console.log(user);
user.then(user => console.log(user));

1.2 async 사용

  • async를 사용하면 함수의 코드 블록이 자동으로 Promise로 변환이 되어짐 (대박!)
// 1. 함수 선언식
async function fetchUser() {
  return `david`;
}

// 2. 함수 표현식
const fetchUser = async function() {
  return `david`;
};

// 3. 화살표 함수
const fetchUser = async () => {
  return `david`;
};

// fetchUser().then(data => console.log(data)); // 함수로 바로 호출
const user = fetchUser(); // 변수에 할당해서 호출
user.then(data => console.log(data));
console.log(user);

2. await

2.1 기존 방식(Promise)

  • 콜백 지옥이 떠오름..
function delay(ms) {
  return new Promise (resolve => setTimeout(resolve, ms));
}

function getApple() {
  return delay(1000)
  .then(() => `🍎`);
}
function getBanana() {
  return delay(1000)
  .then(() => `🍌`);
}

function pickFruits() {
  return getApple()
  .then(apple => {
    return getBanana().then(banana => `${apple} + ${banana}`);
  });
}
pickFruits().then(result => console.log(result));

2.2 await 사용 (error 처리: try/catch문)

  • await는 async 함수 내부에만 사용 가능
  • getApple과 getBanana는 서로 독립적인 코드인데 순차적으로 실행됨 → 비효율
function delay(ms) {
  return new Promise (resolve => setTimeout(resolve, ms));
}

async function getApple() {
  await delay(1000);
  // throw new Error(`error: apple`); // error 발생
  return `🍎`;
}
async function getBanana() {
  await delay(1000);
  // throw new Error(`error: banana`);
  return `🍌`;
}

async function pickFruits() {
  let apple = null;
  try {
    apple = await getApple();
  } catch(error) {
    console.log(error);
  }
  let banana = null;
  try {
    banana = await getBanana();
  } catch(error) {
    console.log(error);
  }
  return `${apple} + ${banana}`;
}
pickFruits().then(result => console.log(result));

2.3 await 병렬 처리

function delay(ms) {
  return new Promise (resolve => setTimeout(resolve, ms));
}

async function getApple() {
  await delay(1000);
  return `🍎`;
}
async function getBanana() {
  await delay(1000);
  return `🍌`;
}

// 방법 1: 무식한 코드
async function pickFruits() {
  // 프로미스 객체는 선언 즉시 바로 실행됨
  // getApple과 getBanana는 병렬(독립적)로 실행됨
  const applePromise = getApple();
  const bananaPromise = getBanana();
  // 동기화
  const apple = await applePromise; 
  const banana = await bananaPromise;
  return `${apple} + ${banana}`;
}
pickFruits().then(result => console.log(result));

// 방법 2: Promise APIs 사용
function pickAllFruits() {
  return Promise.all([getApple(), getBanana()]).then(fruits => {
    return fruits.join(` + `);
  });
	// return Promise.all([getApple(), getBanana()]);
}
pickAllFruits().then(console.log);

// 번외: 비동기 처리중 먼저 리턴하는 녀석만 출력
function pickOnlyOne() {
  return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log);
profile
Data Engineer / Back-End Developer

0개의 댓글