콜백함수

싱글 쓰레드 기반인 자바스크립트에서 비동기 처리를 위해서 콜백함수를 사용한다. 예를 들어 대표적인 콜백함수는 setTimeout(callback, milliseconds)가 있다.

function getData(callbackFunc) {
	$.get('https://domain.com/products/1', function (response) {
		// 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨준다
		callbackFunc(response); 
	});
}

getData(function (tableData) {
	// $.get()의 response 값이 tableData에 전달된다
	console.log(tableData); 
});

콜백함수의 단점

비동기 처리 로직을 위해 콜백함수를 연속으로 사용하는 경우에 콜백 지옥이 발생할 수 있다. 콜백 지옥은 코드의 가독성을 떨어뜨림과 동시에 해당 로직의 변경이 어려워질 수 있다.

콜백 지옥 해결 방법

각각의 콜백함수를 분리하여 작성해주거나, promise 또는 aysnc/await 구문을 사용하여 개선할 수 있다.

Promise

ECMA Script6에 정식으로 추가된 문법으로 비동기 계산을 위해 사용되는 객체이다.
promise의 뜻 그대로 약속으로 이해하면 좀 더 쉬운 것 같다.
즉, “지금은 아니지만, 곧 해당 약속한 부분을 완료하겠다”라는 뜻으로….

구문

new Promise( /* executor */ function(resolve, reject) { ... } );

promise의 상태

promise는 다음 중 하나의 상태를 가진다.

  • pending - 초기 상태로써 아직 대기 중인 상태
  • fulfilled - 수행이 성공하였음
  • rejected - 수행이 실패하였음
  • settled - 수행이 완료된 상태

예시

// From Jake Archibald's Promises and Back:
// http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest

function get() {
  return new Promise(function(resolve, reject) {
  	const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
  	fetch(endpoint)
    	.then(res => res.json())
        .then(data => resolve(data))
        .catch(error => reject(error));
  });
}

// Use it!
// 1. then()으로 에러를 처리하는 경우
get().then(function (response) {
  console.log("Success!", response);
}, function (error) {
  console.error("Failed!", error);
});


// 2. catch()로 에러를 처리하는 경우
get().then((response) => {
  console.log("Success!", response);
}).catch(error => {
  console.error("Failed!", error);
});

Generator (co)

co는 Generator를 이용한 control flow 모듈로써 내부적으로 promise를 사용하여 구현되어 있다.

예제

var co = require('co');

co(function *(){
  // yield any promise
  var result = yield Promise.resolve(true);
}).catch(onerror);

co(function *(){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield [a, b, c];
  console.log(res);
  // => [1, 2, 3]
}).catch(onerror);

// errors can be try/catched
co(function *(){
  try {
    yield Promise.reject(new Error('boom'));
  } catch (err) {
    console.error(err.message); // "boom"
 }
}).catch(onerror);

function onerror(err) {
  // log any uncaught errors
  // co will not throw any errors you do not handle!!!
  // HANDLE ALL YOUR ERRORS!!!
  console.error(err.stack);
}

Async/Await

ECMAScript 2017에서 표준으로 정의되었다. 비동기 프로그래밍을 동기 방식처럼 직관적으로 표현할 수 있다는 장점이 있다.

async/await의 목적은 프로미스의 이용을 쉽게하는 것이다.

function getNumber1() {
    return Promise.resolve('374');
}

async function getNumber2() {
    return 374;
}

aysnc/await 코드를 이용한 코드 작성 팁

####### 클린코드 aysnc/await를 이용하면 훨씬 간결한 코드를 작성할 수 있다.

/*
rp(‘https://api.example.com/endpoint1').then(function(data) {
 // …
});
*/
var response = await rp(https://api.example.com/endpoint1');

####### 에러처리 동일한 코드 구조로 비동기 코드와 동기 코드의 에러를 처리하는 것이 가능하다.

/*
function loadData() {
    try { // Catches synchronous errors.
        getJSON().then(function(response) {
            var parsed = JSON.parse(response);
            console.log(parsed);
        }).catch(function(e) { // Catches asynchronous errors
            console.log(e); 
        });
    } catch(e) {
        console.log(e);
    }
}
*/
async function loadData() {
    try {
        var data = JSON.parse(await getJSON());
        console.log(data);
    } catch(e) {
        console.log(e);
    }
}

####### 조건문 작성 훨씬 직관적인 조건문 작성이 가능하다.

/*
function loadData() {
  return getJSON()
    .then(function(response) {
      if (response.needsAnotherRequest) {
        return makeAnotherRequest(response)
          .then(function(anotherResponse) {
            console.log(anotherResponse)
            return anotherResponse
          })
      } else {
        console.log(response)
        return response
      }
    })
}
*/
async function loadData() {
  var response = await getJSON();
  if (response.needsAnotherRequest) {
    var anotherResponse = await makeAnotherRequest(response);
    console.log(anotherResponse)
    return anotherResponse
  } else {
    console.log(response);
    return response;    
  }
}
스택프레임

프로미스 체인에서 반환된 에러 스택은 어디에서 에러가 발생했는지에 대한 정보를 주지 않는 반면, aysnc/await로 구현하면 에러 스택을 통하여 에러가 발생한 위치를 확인할 수 있다.

디버깅

aysnc/await의 경우에는 개발자 도구에서 디버깅이 프로미스 보다는 수월하다.

Reference