Debugging Asynchronous JavaScript with Chrome DevTools

HTML5 Rocks

소개

콜백 함수를 통해 비동기로 동작할 수 있는 능력은 자바스크립트를 특별하게 만드는 강력한 기능입니다. 비동기 콜백 등록은 이벤트 기반 코드를 작성하게 합니다. 하지만 자바스크립트는 선형 방식으로 실행하지 않아 머리 싸매고 버그를 찾는 경험을 하게 됩니다.

다행히 이제 크롬 카나리 개발자 도구(DevTools)에서 비동기 자바스크립트 콜백의 콜 스택 전부를 볼 수 있습니다.

비동기 콜 스택 빠른 개요 예고편
짧은 예고편 - 비동기 콜 스택 개요

(곧 이 데모의 흐름(flow)을 분석합니다.)

개발자 도구에서 비동기 콜 스택 기능을 활성화하면 다양한 지점에서 그 시간에 맞는 웹 앱의 상태를 자세히 볼 수 있습니다. 일부 이벤트 리스너, setInterval, setTimeout, XMLHttpRequest, Promise, requestAnimationFrame, MutationObservers 등을 위한 스택 전부를 추적합니다.

스택을 추적해 가면서 런타임 실행에서 특정 지점의 어떤 변수 값을 분석할 수도 있습니다. 이것은 Watch Expressions(역자주: 표현식 보기)를 위한 일종의 타임머신과 같습니다.

이 기능을 사용하여 두 세 가지 시나리오를 살펴보도록 합시다.

크롬 카나리에서 비동기 디버깅 사용하기

이 새로운 기능을 크롬 카나리(빌드 35 이상)에서 사용하여 테스트해 보도록 하세요. 크롬 카나리 개발자 도구의 Sources 패널에 들어가 보세요.

오른쪽 Call Stack(콜 스택) 패널 옆에 새로운 "Aysnc(역자주: 비동기)" 체크박스가 있습니다. 체크박스를 선택하고 해제하여 비동기 디버깅을 활성화하거나 비활성화합니다. (일단 사용하면 다시 끄고 싶지 않을지 모릅니다.)

비동기 기능 켜고 끄기

지연 타이머 이벤트와 XHR 응답 캡처하기

아마도 지메일에서 다음과 같은 것을 본 적이 있을 것입니다.

이메일 전송을 재시도하는 지메일

(서버에 문제가 있거나 클라이언트 측 네트워크 연결에 문제가 있거나) 요청 전송에 문제가 있으면 지메일은 짧은 타임아웃 후에 자동으로 메시지를 재전송하려고 합니다.

비동기 콜 스택이 지연 타이머 이벤트 및 XHR 응답 분석을 도와주는 방법을 보려고 모의 지메일 예제로 흐름을 다시 만들었습니다. 전체 자바스크립트 코드는 위 링크에서 확인할 수 있으며 흐름은 다음과 같습니다.

모의 지메일 예제의 흐름도
위 다이어그램에서 파란색으로 강조한 메소드는 비동기로 동작해 개발자 도구의 새로운 기능에 큰 도움이 되는 중요한 지점입니다.

이전 버전 개발자 도구에서는 Call Stack 패널을 본다고 어디에서 postOnFail()를 호출하고 있는지에 대한 정보를 postOnFail() 내부 중단점(Breakpoint)에서 제공하지 않습니다. 하지만 비동기 스택을 활성화할 때 차이점을 보십시오.

비동기 콜 스택을 사용하지 않고 모의 지메일에 중단점 설정
async를 비활성화한 Call Stack 패널

postOnFail()이 AJAX 콜백에서 시작했다는 정보만 알 수 있습니다.

비동기 콜 스택을 사용하고 모의 지메일에 중단점 설정
async를 활성화한 Call Stack 패널

XHR이 submitHandler()에서 시작한 것을 알 수 있습니다. 좋군요!

콜 스택의 async를 켜면 콜 스택 전부를 볼 수 있으므로 아래와 같이 요청이 (submit 버튼을 클릭한 뒤에 호출되는) submitHandler()으로부터 시작되었는지 아니면 retrySubmit()에서 시작했는지 쉽게 알 수 있습니다.

submitHandler()
비동기 호출 스택을 이용하여 가짜 Gmail 예제에서 중단점(Breakpoint) 설정하기

비동기 호출 스택을 이용하여 가짜 Gmail 예제에서 중단점(Breakpoint) 설정하기

retrySubmit()
비동기 호출 스택을 이용하여 가짜 Gmail 예제에서 또다른 중단점(Breakpoint) 설정하기

비동기 호출 스택을 이용하여 가짜 Gmail 예제에서 또다른 중단점(Breakpoint) 설정하기

비동기로 표현식(expressions) 감시하기

전체 콜 스택을 이동해가면 감시하는 표현식도 그 시점 그 곳의 상태를 반영해 업데이트합니다.

비동기 콜 스택을 활성화하고 Watch Expressions를 사용한 예

과거 유효 범위(scope)에서 코드 실행하기

Watch Expressions뿐만 아니라 개발자 도구 자바스크립트 콘솔 패널에서 이전 유효 범위의 코드와 바로 상호작용할 수 있습니다.

여러분이 닥터후이고 타디스(Tardis, 역자주: 영국 드라마 '닥터후'에 나오는 타임머신)에 들어가기 전부터 "지금"까지 시간을 비교하는 데 도움이 약간 필요하다고 생각해보세요. 개발자 도구 콘솔에서 다른 실행점의 값을 쉽게 실행, 저장, 계산할 수 있습니다.

비동기 콜 스택을 활성화하고 자바스크립트 콘솔을 사용한 예
비동기 콜 스택을 활성화하고 자바스크립트 콘솔을 사용하여 코드를 디버깅하세요. 위 데모는 여기에서 볼 수 있습니다.

개발자 도구에 머물면서 표현식을 다루면 소스 코드에 가서 수정하고 브라우저를 새로고침하는 시간을 줄여줍니다.

개봉박두: 연쇄(Chained) Promise Resolution 풀어내기

비동기 콜 스택 기능을 제공하지 않고는 앞에서 본 모의 지메일의 흐름을 펼쳐보이기 어렵다고 생각하고 있다면, 연쇄 Promise처럼 더 복잡한 비동기 흐름은 얼마나 더 어려울지 상상할 수 있습니까? Jake Archibald의 자바스크립트 Promise 튜토리얼 마지막 예제를 다시 봅시다.

자바스크립트 Promise의 흐름도.

Jake의 async-best-example.html 예제에서의 콜 스택을 살펴보는 애니메이션이 있습니다.

Breakpoint set in promises example without async call stacks
비동기를 비활성화한 Call Stack 패널

Promise를 디버깅할 때 Call Stack 패널에 정보가 얼마나 적은지 살펴보세요.

Breakpoint set in promises example with async call stacks
비동기를 활성화한 Call Stack 패널

와우! 앞에서 말한 Promise들. 수많은 콜백.

Promise 구현이 Blink 버전에서 V8의 최종 버전으로 전환하고 있으므로 조만간 Call Stack이 Promise를 지원할 것입니다.

그 시점으로 돌아가기를 염두에 두고 현재 Promise를 위한 비동기 Call Stack 미리 보기를 원한다면 크롬 33 혹은 크롬 34에서 확인 가능합니다. chrome://flags/#enable-devtools-experiments에서 개발자 도구 실험을 사용합니다. 다시 카나리를 실행하고 개발자 도구 설정에 가면 Enable support for async stack traces(비동기 스택 추적 지원 사용하기) 옵션이 있습니다.

웹 애니메이션 이해하기

HTML5Rocks 기록을 더 자세히 살펴보도록 합시다. Paul Lewis의 Leaner, Meaner, Faster Animations with requestAnimationFrame?을 기억하십니까?

requestAnimationFrame 데모를 열고 post.html의 update() 메소드의 시작 위치(874 라인 근처)에 중단점을 추가합니다. 비동기 호출 스택을 이용하면 스크롤 이벤트 콜백의 발생으로 되돌아가며 살펴볼 수 있는 모든 기능을 포함하여 requestAnimationFrame를 파악하기 위한 더 많은 통찰력을 얻을 수 있습니다.

비동기 콜 스택을 사용하지 않고 requestAnimationFrame에서 중단점 설정
비동기를 비활성화한 Call Stack 패널
후 After
비동기 콜 스택을 사용하고 requestAnimationFrame에서 중단점 설정
그리고 비동기 활성화하기

MutationObserver를 사용할 때 DOM 업데이트 추적하기

MutationObserver는 DOM의 변화를 관찰합니다. 간단한 예제에서 버튼을 클릭하면 <div class="rows"></div>에 새로운 DOM 노드를 추가합니다.

demo.html에서 nodeAdded()(31번째 줄)에 중단점을 추가합니다. 비동기 콜 스택을 켜고 addNode()를 지나 처음의 클릭 이벤트까지 콜 스택을 거꾸로 살펴볼 수 있습니다.

Breakpoint set in mutationObserver example without async call stacks
비동기를 비활성화한 Call Stack 패널
Breakpoint set in mutationObserver example with async call stacks
그리고 비동기를 활성화한 Call Stack 패널

비동기 콜 스택에서의 자바스크립트 디버깅 팁

함수에 이름을 주세요

모든 콜백을 익명 함수(anonymous function)로 주는 경향이 있다면 대신 이름을 부여하여 콜 스택을 쉽게 볼 수 있을 것입니다.

예를 들어 아래처럼 익명 함수를 전달합니다.

window.addEventListener('load', function(){
  // 무엇인가 하기
});

그리고 windowLoaded() 같이 이름을 부여합니다.

window.addEventListener('load', function windowLoaded(){
  // 무엇인가 하기
});

load 이벤트가 발생할 때 개발자 도구 스택 추적에서 암호 같은 "(anonymous function)" 대신 함수 이름이 보일 것입니다. 이로써 스택 추적에서 어떤 일이 일어나고 있는지 더 쉽게 살펴볼 수 있습니다.

익명 함수An anonymous function
이름 있는 함수A named function

더 살펴보기

요약하면 아래 모두는 개발자 도구에서 콜 스택 전부를 보게 될 비동기 콜백입니다.

  • 타이머: setTimeout()이나 setInterval()을 초기화한 곳으로 돌아갑니다.
  • XHR: xhr.send()를 호출한 곳으로 돌아갑니다.
  • 애니메이션 프레임: requestAnimationFrame을 호출한 곳으로 돌아갑니다.
  • MutationObserver: Mutation Observer 이벤트가 발생한 곳으로 돌아갑니다.
  • addEventListener()를 통한 적정한 DOM 이벤트들: 이벤트가 발생한 곳으로 돌아갑니다. 성능 상의 이유로 인해 모든 DOM 이벤트들이 비동기 호출 스택 기능에 적합하지는 않습니다. 'scroll', 'hashchange' 그리고 'selectionchange'가 현재 가능한 이벤트들의 예입니다.
  • addEventListener()를 통한 멀티미디어 이벤트들: 이벤트가 발생한 곳으로 돌아갑니다. (예를 들어 'play', 'pause', 'ratechage'와 같은) 오디오와 비디오 이벤트, WebRTC의 MediaStreamTrackList 이벤트('addtrack', 'removetrack') 그리고 MediaSource 이벤트('sourceopen')이 현재 가능한 멀티미디어 이벤트들입니다.

이러한 실험용 자바스크립트 API에 대한 전체 콜 스택을 곧 지원할 것입니다.

  • Promise: Promise가 해결된(resolved) 곳으로 돌아갑니다.
  • Object.observe: 옵저버(Observer) 콜백을 처음에 바인딩한 곳으로 돌아갑니다.

자바스크립트 콜백의 스택 전부를 추적할 수 있다는 것이 여러분을 차분하게 해줄 것입니다. 다수의 비동기 이벤트가 서로 관련지어 발생하거나, 비동기 콜백 내에서 캐치(Catch) 되지 않은 예외(Exception)를 던진다면 개발자 도구의 이 기능이 특히 유용할 것입니다.

크롬 카나리에서 해보세요. 이 새로운 기능에 대한 피드백이 있으면 크롬 개발자 도구(Chrome DevTools)의 버그 추적기(Bug Tracker)Chrome DevTools Group에 알려주세요.

Comments

0