Callback/Promise/Async&Await

Sunwoo·2021년 5월 11일
0

자바스크립트는 동기적이다. 호이스팅(선언이 자동으로 제일 위로 올라가는 것) 이후 순서대로 동기적으로 진행된다는 말이다. 동기는 뭘까? 프로그램 언어는 생소하게 느껴지는 것이 많은 것 같다. 프로그램은 말 그대로 순서이다. 예를 들어보자.

'use strict';
console.log("1");
setTimeout(function() {
    console.log("2");
}, 1000);
console.log("3");
// 1
// 3
// 2

콜백함수는 말 그대로 함수를 인자로 받았을 때, 나중에 내가 그 함수를 불러서 쓰겠다는 놈이다. 대표적으로 setTimeout(함수, 시간) 안에 인자로 함수를 넣어서 처리할 때 그 놈이 콜백함수이다. 또한 setTimeout은 대표적인 비동기처리 함수이다.

비동기는 뭘까. 이것을 대기표로 많이 비유한다. 동기는 놀이공원에서 기구를 타기 위해서 계속 줄 서서 있다가 끝나면 화장실을 가는 거라면, 비동기는 번호표를 뽑아서 줄을 서지 않고 화장실을 갔다와서 자기 차례가 오면 바로 입장하는 그런 경우이다.

그래서 위의 코드처럼 콘솔로그 함수3은 셋타임 '2' 가 출력되는 것을 기다려주지 않고 진행되고 대기표를 받았던 셋타임 2는 1000ms 시간이 지나고 자리 차례가 되자 그제서야 '2' 를 출력하게 되는 것이다.

콜백은 무조건 비동기는 아니다.

function printImmediately(print) {
    print();
}
printImmediately(() => console.log('hello'));

이는 콜백함수를 받고 있지만 바로 콘솔로그가 출력되도록 하고 있다.

function printDelay(print, timeout) {
    setTimeout(print, timeout);
}
printDelay(() => console.log('hello'), 2000);

이는 콜백함수가 비동기 처리하는 셋타임 함수이기 때문에 2초 뒤에 작업하는 것을 알 수 있다.

//콜백예시!
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = promt('enter your password');
userStorage.loginUser(
	id,
	password,
	user => {
		userStorage.getRoles(
			user,
			userWithRole => {
				alert(`당신의 이름은.. 당신의 권한은..`);
			},
			error => {
				console.log(error);
			}
		);
	},
	error => {
		console.log(error);
	}
);

Promise

프로미스는 비동기 처리를 도와주는 오브젝트이다.

기능을 수행하면 정상적/비정상 처리에 따른 정상 처리나 에러를 알려준다.

정보를 제공하는 프로듀서와 정보를 소비하는 소비자를 염두해두자.

state: pending → fulfilled or rejected

resolve와 reject를 실행하지 않으면 그안에 값은 pending 상태로 유지된다.

Producer vs Consumer

프로미서는 클래스로 인스턴스를 생성할 수 있다.

인자로 executor 함수를 받는데 resolve와 reject를 받아서 처리한다.

헤비한 처리를 할 때 비동기를 처리하지 않으면 계속 기다리기 때문에 좋지 않다.

프로미스를 만드는 순간 바로 엑스큐터 함수가 실행된다. 따라서 사용자가 버튼을 눌렀을 때만 네트워크가 실행해야 한다면 바로 엑스큐터 함수가 실행되지 않도록 주의해야한다.

"새로운 프로미스 객체가 만들어질 때는 자동으로 엑스큐터가 실행된다."

만들어진 프로미스를 사용하는 사용자를 Consumer라고 한다.

프로미스가 실행되고 나면 그 결과를 then() 함수를 이용해서 받을 수 있다. 만약 프로미스가 에러 객체를 받아온다면 catch() 함수를 이용해서 처리할 수 있다.

프로미스의 then()은 다시 프로미스를 반환하기 때문에 catch()로 체이닝할 수 있다.

finally()는 성공하든 실패하든 받아서 처리하는 함수이다.

//프로미스 체이닝
'use strict';

const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000) // 1초후 1을 넘김
})

fetchNumber
  .then(num => num * 2) //2
  .then(num => num * 3) //6
  .then(num => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num -1), 1000); // 1초 후 1을 뺌
    })
  })
  .then(num => console.log(num)); // 2초 걸려서 5 출력
//프로미스 에러처리
const getHen = () =>
  new Promise((resolve, reject) => {
  setTimeout(() => resolve('🐓'), 1000); 
});
const getEgg = hen =>
  new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000); 
});
const cook = egg =>
  new Promise((resolve, reject) => {
  setTimeout(() => resolve('🥠'), 1000); 
});
 

getHen() // 
  .then(getEgg)
  .then(cook)
  .then(console.log)
  .then(console.log);
//함수 인자가 하나일 경우 축약할 수 있다.
getHen() // 
  .then(hen => getEgg(hen))
  .then(egg => cook(egg))
  .then(meal => console.log(meal));
//위의 함수를 아래로 짧게 축약할 수 있다. 
//조건은 함수의 인자가 같은 이름으로 하나만 전달될 경우이다.
getHen().then(getEgg).then(cook).then(console.log).then(console.log);
//getHen에서 받은 결과값(하나)을 getEgg(결과값)에 그대로 전달하고... 그 결과값을...

Async & Await

프로미스는 체이닝을 계속하게 되면 난잡하게 된다.

Async Await는 순서대로 작성하는 것처럼 도와주며, 이는 새로운 기술이 아니라 기존의 Promise 위에 조금 더 간편하게 사용하기 위한 API를 제공한다. 이렇게 기존의 것을 감싸서 더 사용하게 좋게 하는 것을 Syntactic Sugar라고 한다.

결국 Async Await는 Promise에 편의를 위한 api가 추가된 것이라고 보면 된다.

// sync(동기)
function fetchUser() {
    // 10초 소요되는 작업
    return '홍길동';
  }

  const user = fetchUser(); // 10초 후 
  console.log(user); // 홍길동 결과 출력

// Async(비동기) 키워드를 붙이면 프로미스가 된다. 
async function fetchUser() {
    // 10초 소요되는 작업
    return '홍길동';
  }

  const user = await fetchUser(); // 비동기처리가 끝날 때까지 기다려줌
  console.log(user);

함수 앞에 async 라는 키워드를 쓰는 것으로 함수가 promise로 변환되게 된다.

이 데이터를 받을 때는 await로 받을 수 있다. 본래 작업이 끝날 때까지 기다려주는 동기와 달리 비동기는 따로 처리하기 때문에 비동기로 할 때 작업의 순서가 필요하다면 async로 프로미스로 만들고, await로 작업이 끝날 때까지 기다렸다가 처리한다. (이를 통해서 병렬 처리로도 가능하다)

실제로 병렬 처리할 때 async await를 쉽게 해주는 api가 존재한다. Promise의 all() 함수를 이용하면 모든 프로미스가 끝날 때까지 모아준다. (배열로 반환)

//병렬처리
function pickAllFruits() {
    return Promise.all([getApple(), getBanana()]).then(fruits => 
      fruits.join(' + ') // 배열 안의 값을 합쳐주는 api
    );
  }

//가장 먼저 리턴하는 값만 출력
function pickOnlyOne() {
    return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log); // '사과'
profile
한 줄 소개 너무나 어려운 질문이다.

0개의 댓글