You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
콜백은 자바스크립트에서 비동기성을 표현하고 관리하는 가장 일반적인 기법이자, 사실상 자바스크립트 언어에서 가장 기본적인 비동기 패턴이다. 이번 장에서는 콜백의 실체를 깊이 있게 살펴보고 이보다 더 정교한 비동기 패턴이 나오게 된 계기를 살펴본다.
2.1 연속성
// Aajax("..",function(..){// C});// B
A와 B는 지금, C는 나중에 해당한다.
콜백 함수는 프로그램의 연속성을 감싼/캡슐화한 장치다.
2.2 두뇌는 순차적이다
인간이 정말 동시에 두 개의 의식을 갖고 의도적인 행동을 하면서 정확히 같은 순간에 두 가지 일을 한꺼번에 생각/추론할 수 있을까? 두뇌 기능이 최고 수준에 이른다면 과연 이러한 병렬 멀티스레딩이 가능할까?
아니오
인간의 두뇌는 그렇게 만들어지지 않았다. 인간은 싱글태스커에 더 가깝다.
멀티태스킹을 하는 것처럼 보이는 상황도 실은 우리가 아주 재빠른 콘텍스트 교환기 Context Switcher처럼 행동하고 있을 뿐이다. 다시 말해 여러 작업 사이를 재빨리 연속적으로 왔다갔다 하면서 각 작업을 아주 작고 짧은 덩이로 쪼개어 동시에 처리하는 것이다. 인간의 두뇌는 이벤트 루프 큐처럼 작동한다.
어느 날 문제가 발생했다. 고객 한명이 TV를 구매했는데, 다섯 번이나 연속 결제됐다며 열받아서 이야기한다.
로그 파일을 캐보니 원인은 하나, 위 추적 유틸리티가 콜백 함수를 한 번만 호출하는 대신 다섯 차례나 연달아 불렀다.
알고 보니, 솔루션 업체가 납품한 코드 중 초당 한 번씩 주어진 콜백 함수를 호출하는데 최대 5초 동안 재시도하다가 타임아웃 에러를 내도록 만들어진 테스트 단계의 코드가 포함된 것이 문제였다. 이것이 서드 파티에 제어권이 있는 코드의 취약점이다.
개미 굴을 파는 심정으로 콜백 호출 시 오류가 날만한 경우의 수를 모두 따져보기로 했다:
콜백을 너무 일찍 부르는 경우
콜백을 너무 늦게 부르는 경우
콜백을 너무 적게 또는 너무 많이 부르는 경우
필요한 환경/인자를 정상적으로 콜백에 전달하지 못하는 경우
일어날지 모르는 에러나 예외는 무시...
그런데 이렇게 모든 경우별로 보완 로직을 구현해 넣는다는 게 얼마나 끔찍한 일인지, 그리고 추적 유틸리티에 넘겨주는 콜백이 전적으로 믿을만한지도 이제 의문이다. 이것이 콜백 지옥이다.
2.3.2 남의 코드뿐만 아니라
서드 파티를 안쓰고, 자체 코드 베이스에서 이론적으로 제어하는 유틸리티라면 신뢰할 수 있을까? ... 결국에는 매번 비동기적으로 부를 때마다 콜백 함수에 반복적인 관용 코드/오버헤드를 넣는 식으로 손수 필요한 장치를 넣어주어야 한다. 이렇게 삽입을 해도 완전히 잘못 될 수 있는 게 제어의 역전 문제다.
제어의 역전으로 빚어진 믿지 못할 코드를 완화할 장치가 없는 상황에서 콜백으로 코딩하면 지금 버그를 심어놓은 것이나 다름없다. 잠재적인 버그도 버그는 버그니까.
2.4 콜백을 구하라
에러 우선 스타일 Error-First Style이라는 콜백 패턴(노드 스타일이라고도 불림)을 살펴보자. 이의 콜백 함수는 에러 객체를 첫번째 인자로 받는다. 성공 시 이 인자는 빈/falsy 객체로 채워지지만, 실패 시 truthy 또는 에러 객체로 세팅된다.
functionresponse(err.data){// 에러인가?if(err){console.error(err)}// 아니면 성공한 것으로 본다else{console.log(data)}}ajax("http://some.url.1",response)
실패한 경우든 성공한 경우든, 다음 몇 가지 사실을 알 수 있다.
원하지 않는 반복적인 호출을 방지하거나 걸러내는 콜백 기능이 전혀 없다.
표준적인 패턴의 모습을 띠고 있음에도 불구하고 재사용 불가능한, 장황한 관용 코드라서 실제로 앱을 개발할 때 매 콜백마다 타이핑해야 한다.
콜백을 한번도 호출하지 않는다면? 이런 경우가 중요하게 처리되어야 한다면, 이벤트를 취소하는 타임아웃을 걸어놓아야 한다.
동기냐 비동기냐에 관한 비결정성은 버그를 추적하기 아주 곤욕스럽게 만든다. 주어진 API가 항상 비동기로 작동할지 확신이 없다면? 다음과 같은 유틸리티를 만들어 쓸 수도 있다.
functionasyncify(fn){varorig_fn=fn,intv=setTimeout(function(){intv=nullif(fn)fn()},0)fn=nullreturnfunction(){// 비동기 차례를 지나갔다는 사실을 나타내기 위해// intv 타이머가 기동하기도 전에 너무 빨리 발사if(intv){fn=orig_fn.bind.apply(orig_fn,// 인자로 전달된 값들을 커링하면서 this에 bind() 호출 인자를 추가한다[this].concat([].slice.call(arguments)))}// 이미 비동기다else{orig_fn.apply(this,arguments)}}}// 아래와 같이 asyncify 함수를 사용할 수 있다functionresult(data){console.log(a)}vara=0ajax("..미리 캐시된 URL..",asyncify(result))a++
2.5 정리하기
콜백은 자바스크립트에서 비동기성을 표현하는 기본 단위다.
그러나 자바스크립트와 더불어 점점 진화하는 비동기 프로그래밍 환경에서 콜백만으로는 충분치 않다.
사람의 두뇌는 순차적, 중단적, 단일-스레드 방식으로 계획하는 데 익숙한데, 콜백은 비동기 흐름은 비선형적, 비순차적인 방향으로 나타내므로 구현된 코드를 제대로 이해하기가 매우 어렵다. 추론하기 곤란한 코드는 곧 악성 버그를 품은 나쁜 코드로 이어진다. 그래서 비동기성을 좀더 동기적, 순차적, 중단적인 모습으로, 즉 우리 두뇌가 사고하는 방식과 유사하게 표현할 방법이 필요하다.
콜백은 프로그램을 진행하기 위해 제어를 역전, 즉 제어권을 다른 파트에 암시적으로 넘겨줘야 하므로 골치가 아프다. 이런 문제를 해결하기 위해 임시 로직을 짜넣으면 당장은 난관을 모면할 수는 있지만, 생각만큼 구현이 쉽지 않고 계속 이런 식으로 하다 보면 거칠고 유지 보수가 어려운 코드가 된다.
콜백을 능가하는 뭔가, 더욱 정교하고 역량있는 비동기 패턴이 절실하게 필요하다. 바로 프라미스 Promise와 그 이후에 나타난 최신 기술들이다.