React | Callback, Promise, async await

샘샘·2023년 5월 4일
0

React

목록 보기
18/28
post-thumbnail

드림코딩

자바스크립트는 synchronous이다 (동기식)
synchronous : Hoisting 후 순서대로 코드 실행
👉 코드가 나타나는 순서대로 실행된다

console.log('1')
console.log('2')
console.log('3')

순서대로 실행되어서 1 2 3 순으로 출력됨

asynchronous (비동기식)

console.log('1')
setTimeout(() => console.log('2'), 1000)
console.log('3')

순서대로 실행되었지만, 1초 뒤에 실행하는 setTimeout 함수에 console.log('2')가 callback 되어 1 3 2 순으로 출력됨

콜백지옥

class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === 'ellie' && password === 'dream') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSuccess(id);
      } else {
        onError(new Error('not found'));
      }
    }, 2000);
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === 'ellie') {
        onSuccess({ name: 'ellie', role: 'admin' });
      } else {
        onError(new Error('no access'));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your passrod');
userStorage.loginUser(
  id,
  password,
  user => {
    userStorage.getRoles(
      user,
      userWithRole => {
        alert(
          `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
      },
      error => {
        console.log(error);
      }
    );
  },
  error => {
    console.log(error);
  }
);

콜백지옥이란 비동기 프로그래밍시 발생하는 문제로, 함수 안에서 또 함수를 호출하고... 의 반복인 함수를 말한다
보통 콜백지옥은 뒤집힌 C자 커브의 모양을 나타낸다
1. 가독성이 좋지 않음
2. 코드 수정이 어려움

아래는 콜백 지옥을 해결하기 위한 방법들이다

Promise

자바스크립트 안에 내장되어있는 오브젝트
비동기적인 것을 수행할 때, 콜백함수 대신 유용하게 쓰일 수 있다
1. state (상태) : pending -> fulfuilled or rejected
2. producer(정보 제공자)와 consumer(정보 소비자)의 차이

Producer

새로운 Promise가 만들어 질 때, 전달한 executor 콜백 함수가 바로 실행 된다
사용자가 버튼을 눌렀을 때, 네트워크 요청을 해야되는 경우 등 => 불필요한 통신이 일어날 수 있으니 사용 하지 않는 것을 권장

const promise = new Promise((resolve, reject) => {
  // doing some heavy work (network, read files)
  // 보통 무거운 작업들을 비동기적으로 받아옴(시간이 걸리기 때문)
  setTimeout(() => {
  	1. resolve('ellie') // 성공했을 때
 	 }, 2000)
    2. reject(new Error('no network')) // 실패했을 때
})

Promise 안에는 executor 함수를 콜백 받고, 그 안에서 resolve, reject 함수를 콜백 받는다

Consumers

then, catch, finally

promise.then((value => { // Promise가 잘 전달되어서 받은 값을 then으로 전달
	console.log(value)
})
  .catch(error => { // 에러가 발생했을 때 어떻게 처리할 것인지
      console.log(error)
})
  .finally(() => { // 결과가 어떻든 수행 됨
  console.log('finally')
})

Promise chaining

const fetchNumber = new Promise((resolve, reject) => {
	setTimeout(() => resolve(1), 1000) // 1초 뒤에 1을 출력하게
})

fetchNumber
.then(num => num * 2) // resolve된 값에 2를 곱하고 = 2
.then(num => num * 3) // 위 then에 3을 곱하고 = 6
.then(num => {
  return new Promise((resolve, reject) => { // 위 then에 다른 서버와 통신하는 새 Promise를 리턴
  	setTimeout(() => resolve(num - 1), 1000) // = 5
  })
})
.then(num => console.log(num)) // 위 동작을 다 실행하고 난 뒤에 실행 = 3초 뒤에 5 출력

Error Handling

const getHen = () =>
	new Promise((reslove, reject) => {
      setTimeout(() => resolve('🐔'), 1000)
    })
const getEgg = hen =>
	new Promise((reslove, reject) => {
      setTimeout(() => 1. resolve(`${hen} => 🥚`), 1000)
		// 네트워크에 문제가 생겼다면?
					   2. reject(new Error(`error! ${hen} => 🥚`), 1000)
    })
const cook = egg =>
	new Promise((reslove, reject) => {
      setTimeout(() => resolve(`${egg} => 🍳`), 1000)
    })

getHen()
	.then(getEgg) // (hen => getEgg(gen))
4.  .catch(error => { // 계란을 받아올 때 문제가 생긴다면,
		return '🥖' // 대신 빵을 받아오겠다
	})
	.then(cook) // (egg => cook(egg))
	.then(console.log) // (meal => console.log(meal))
3.  .catch(console.log)
  1. 정상 실행 됐을 때 3초 뒤 결과값
    🐔 => 🥚 => 🍳

  2. 에러가 생겼을 때의 결과값

  3. 에러가 생겼을 때, 마지막 줄에 catch를 추가했을 때의 결과값 (어디에서 에러가 났는지 찾을 수 있음)

  4. 계란을 받아올 때 에러가 생겨서 계란은 못 받았지만, 대신 빵을 받아서 결과값인 cook을 출력할 수 있음

👉 Promise 체인이 실패하지 않고 오류가 잘 처리 되었다
순서를 변경해가며 어떻게 해야 오류가 안 날지 고민할 것

콜백지옥 해결

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === 'ellie' && password === 'dream') ||
          (id === 'coder' && password === 'academy')
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    });
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === 'ellie') {
          resolve({ name: 'ellie', role: 'admin' });
        } else {
          reject(new Error('no access'));
        }
      }, 1000);
    });
  }
  
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your passrod');
userStorage
  .loginUser(id, password)
  .then(userStorage.getRoles)
  .then(user => alert(`Hello ${user.name}, you have a ${user.role} role`))
  .catch(console.log);

훨씬 짧고 가독성이 좋아진 것을 확인할 수 있다

async & await

Promise를 더 쉽고 깔끔하게 쓸 수 있음

async

async 코드를 함수 앞에 쓰면 자동으로 Promise로 바뀌어서 인식한다

function fetchUser() {
  return new Promise((resolve, reject) => {
    // do network request in 10 secs...
    resolve('ellie')
  })
}

const user = fetchUser()
user.then(console.log)
console.log(user)
async function fetchUser() {
  // do network request in 10 secs...
  return 'ellie'
}

const user = fetchUser()
user.then(console.log)
console.log(user)

두 함수가 같은 동작을 하지만 밑에 async 함수가 더 짧음

await

awaitasync가 사용된 함수에서만 쓸 수 있음

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
} // ms의 시간이 지나면 resolve를 리턴하는 함수

aysnc function getApple() {
  await delay(3000) // 3초가 지나기까지 기다렸다가
  return '🍎' // 사과를 리턴한다
}

aysnc function getBanana() {
  await delay(3000) // 3초가 지나기까지 기다렸다가
  return '🍌' // 바나나를 리턴한다
}
async function pinkFruits() {
  const apple = await getApple()
  const banana = await getBanana()
  return `${apple} + ${banana}`
} // 6초가 지나면 사과와 바나나가 리턴된다

pickFruits.then(console.log)

기존 Promise 방식을 쓸 때보다 코드가 훨씬 간결해졌지만, 사과가 리턴 될 때까지 기다렸다가 바나나가 나올 필요가 없다면?
👉 병렬 방식을 이용한다

async function pinkFruits() {
  const applePromise = getApple()
  const bananaPromise = getBanana()
  const apple = await applePromise()
  const banana = await bananaPromise()
  return `${apple} + ${banana}`
} // 3초가 지나면 사과와 바나나가 리턴된다

pickFruits.then(console.log)

Promise는 실행되는 순간 바로 값을 전달해주므로, 병렬 방식으로 사과와 바나나가 동시에 동작한다
그렇지만 코드가 지저분함

Promise.all

function pickAllFruits() {
  return Promise.all([getApple(), getBanana()]).then(fruits =>
  .then(fruits.join('+')
  )
}
pinkAllFruits().then(console.log)

위 병렬 방식을 더 깔끔하게 사용하는 방법이다

profile
회계팀 출신 FE개발자 👉콘테크 회사에서 웹개발을 하고 있습니다

0개의 댓글