본문 바로가기

자바스크립트의 정석 🟡

[자바스크립트] 콜백, Promise, async/await

 

✨ 서론

function first() {
	let value;
	
	setTimeout(() => {
		value = {name: 'max', age: 18};
	}, 3000);
	
	return value;
	}
	
	console.log(first()); // undefined
  • 변수 value에 객체를 할당하기 전에 반환했기 때문에, first() 호출문으로 반환된 값은 undefined
first(function (error, value) {
	if(error) {
	}
	else {
	console.log(value);
	}
})
  • foo? 함수의 인자로 콜백함수 넘겨주고 비동기 처리가 끝난 후 콜백함수를 실행하여 정상적으로 데이터를 가지고 옴
  • 콜백함수 호출 시점의 권한이 개발자에게 있는 것이 아니라 제어권을 넘겨받은 코드에게 있음 → 우리는 해당 콜백 함수의 호출을 지켜볼 수밖에 없음

 

✨콜백함수란?

  • 콜백함수는 다른 함수에 매개변수로 넘겨준 함수를 말함
    • 함수를 명시적으로 호출하는 것이 아니라 이벤트가 발생했을 때 시스템에 의해 호출
    • 콜백은 다른 함수(A)의 전달인자로 넘겨주는 함수(B)를 말함
    • 매개변수를 넘겨 받은 함수(A)는 콜백함수 (B)를 필요에 따라 즉시 실행할 수도, 아님 나중에 실행할 수도
  • 콜백지옥
    • JS를 이용한 비동기 프로그래밍시 발생하는 문제 → 매개변수로 넘겨지는 콜백함수가 반복되어 코드의 들여쓰기 수준이 깊어지는 현상
    • 이벤트 처리나 서버통신과 같은 비동기적인 작업을 수행하기 위해 이런 형태가 자주 등장 → 가독성 떨어지고, 코드 수정이 어려워짐
//1
step1(function (value1) {
    step2(function (value2) {
        step3(function (value3) {
            step4(function (value4) {
                step5(function (value5) {
                    step6(function (value6) {
                        // Do something with value6
                    });
                });
            });
        });
    });
});

//2
getData(function(data) {
	processData(data, function(processedData) {
		renderData(processedData, function() {
			console.log('Done!');
		});
	});
});
  • #1) step1에서 어떤 처리 이후 그 결과를 받아와, 인자로 전달된 익명함수의 매개변수를 넘겨줌. 이후 step2에서 어떤 처리를 하고, 다음 익명함수가 실행 → 피라미드 모양으로 기술

 

  • #2)
    • 서버로부터 데이터를 받아옴
    • 가져온 데이터를 처리
    • 데이터를 화면에 출력
  • getData()함수는 서버로부터 데이터를 가져오는 비동기 함수. processData()는 데이터를 처리하는 함수, renderData()는 데이터를 화면에 출력하는 함수 → 각 함수는 콜백함수를 인자로 받아 작업을 수행
  • 콜백함수가 중첩되면서 코드 가독성이 떨어지고 유지보수가 어려움
    • getData 함수가 실패했을때 어떻게 처리할 지, processData함수에서 오류났을 때 또한 예외처리를 추가 해야함 → 이러면 또 코드가 더러워짐

 


  • 해결책) Promise 객체 사용하기
    • Promise 객체를 사용하며 비동기 작업을 순차적으로 처리 → then 메서드를 호출하여 다음 작업을 지정하면, 이전 작업이 완료된 후 다음 작업 실행 ⇒ 콜백지옥에서 벗어남
    • 어떤 작업의 중간 상태를 나타내는 오브젝트 → 미래에 어떤 종류의 결과가 반환됨을 promise 해주는 오브젝트
    • 비동기 작업이 맞이할 미래의 완료 또는 실패와 결과값을 나타냄
    getData()
        .then(processData)
        .then(renderData)
        .then(function() {
        console.log('Done!');
    })
    
    • 비동기 처리에 사용되는 객체 ⇒ 내용은 실행되지만 결과를 아직 반환하지 않은 객체
      • Pending(대기) → Fulfilled(이행) → Rejected(실패)
      • 완료되지 않았다면 대기, 완료되면 이행, 실패나 오류면 Reject 상태
const condition = true;
const promise = new Promise((resolve, reject) => {
  if (condition) {
    resolve('resolved');
  } else {
    reject('rejected');
  }
});

promise
  .then((res) => {
    console.log(res);
  })
  .catch((error) => {
    console.error(error);
  });
  • 결과)
    • conditon 값에 따라 promise의 반환 값이 결정
    • 값이 참이면 resolve, 아니면 reject
    • resolve한 반환 값에 대해서 then()을 통해 결과 값을 반환할 수도
    • reject의 반환 값에 대한 catch()를 통해 반환
    • then(), catch() 문의 체이닝을 통해 비동기 로직의 성공 여부에 따른 분기처리 가능

 


 

✨async/await

  • async, await 적용하기
    • callback이나 promise는 꼬리에 꼬리를 무는 코드가 나올 수도. 흔히 콜백지옥, then 지옥
    • await을 통해 Promise 반환 값을 받아올 수도
    • 하지만 await은 async 함수 안에서만 작동
const variable = await Promise;
//promise 반환값을 받아 variable 

  • 차이점
    • 응답값을 명시적인 변수에 담아 사용하므로 직관적으로 변수를 인식
    • 에러 핸들링
      • Promise는 .catch 문으로 에러 핸들링 가능, async/await은 에러핸들링이 없어서 try-catch() 문을 활용해야 함
    • 코드 가독성
      • Promise의 then() 지옥의 가능성 줄여줌
      • async/await은 비동기 코드가 동기 코드처럼 읽히게 해줌

 

  • Promise.all()
  • Promise.all()은 여러 개의 Promise들을 비동기적으로 실행하여 처리할 수 있도록
  • 여러개의 Promise 들 중 하나라도 reject면 반환하거나 하나라도 에러가 나면 바로 reject

  const result: any[] = await Promise.all([
        fetchNameList(),
        fetchFruits(),
        fetchTechCompanies(),
    ]);

    // time end
    console.timeEnd('promise all example');

    console.log('%j', result);
  • 전체 실행 시간이 402ms. Promise 들의 처리시간은(300, 200, 400ms) 가장 긴 시간인 400ms와 비슷한 시간 → 비동기 처리임

  • 각각 처리 결과를 변수에 담을 수도 있음
const [names, fruits, companies] = await Promise.all([
  fetchNameList(),
  fetchFruits(),
  fetchTechCompanies(),
]);

console.log('%j, %j, %j', names, fruits, companies);

  • 동기 처리를 할려면?
const names: string[] = await fetchNameList();
const fruits: string[] = await fetchFruits();
const companies: string[] = await fetchTechCompanies();

// time end
console.timeEnd('promise all example');

console.log('%j, %j, %j', names, fruits, companies);
  • 905ms. 300ms, 200ms, 400ms을 모두 더한 값 900ms.
  • 서로 영향을 주지 않으면 비동기처리가 훨씬 빠른 속도로 처리됨

  • 에러처리
    • 하나라도 reject거나 에러면 모두 reject

  • 마무리
    • 여러개의 Promise들이 비동기적으로, 정상적으로 끝났음을 알 수 있는 방법
    • 에러를 던지지 않고 정상적으로 종료되었다는 것은 모든 Promise들을 정상적으로 처리되었다는 증거

 

 

끝으로,

⭐ Promise와 Callback를 비교 설명

  • 콜백을 사용하면 비동기 로직의 결과값을 처리하기 위해서 콜백 안에서만 처리를 해야하고 콜백 밖에서는 비동기에서 온값을 알 수 없음
  • promise를 사용하면 비동기에서 온 값이 promise 객체에 저장되기 때문에 코드 작성에 용이
  • 프로미스를 사용하면 프로미스 객체에 비동기가 처리된 결과값이 저장
  • 콜백의 경우 매번 비동기를 실행해야지 그 값을 사용할 수 있지만, 프로미스는 .then 메소드를 통해 저장되어 있는 값을 원하는 때에 사용