Chrome DevTools로 비동기 자바스크립트 디버깅

소개

JavaScript를 고유하게 만드는 강력한 기능은 콜백 함수를 통해 비동기식으로 작동하는 기능입니다. 비동기 콜백을 할당하면 이벤트 기반 코드를 작성할 수 있지만 JavaScript가 선형 방식으로 실행되지 않으므로 버그 추적 작업이 매우 번거로워집니다.

다행히 이제 Chrome DevTools에서 비동기 JavaScript 콜백의 전체 호출 스택을 볼 수 있습니다.

비동기 호출 스택에 대한 간단한 소개입니다.
비동기 호출 스택에 대한 간단한 소개입니다. (이 데모의 흐름은 곧 자세히 설명해 드리겠습니다.)

DevTools에서 비동기 호출 스택 기능을 사용 설정하면 다양한 시점에서 웹 앱의 상태를 자세히 살펴볼 수 있습니다. 일부 이벤트 리스너, setInterval, setTimeout, XMLHttpRequest, 프로미스, requestAnimationFrame, MutationObservers 등의 전체 스택 트레이스를 탐색합니다.

스택 트레이스를 탐색할 때 런타임 실행의 특정 지점에서 변수의 값을 분석할 수도 있습니다. 시계 화면을 위한 타임머신과도 같습니다.

이 기능을 사용 설정하고 이러한 시나리오 중 몇 가지를 살펴보겠습니다.

Chrome에서 비동기 디버깅 사용 설정

Chrome에서 이 새로운 기능을 사용 설정하여 사용해 보세요. Chrome Canary DevTools의 Sources 패널로 이동합니다.

오른쪽의 Call Stack 패널 옆에 'Async'의 새 체크박스가 있습니다. 체크박스를 전환하여 비동기 디버깅을 사용 또는 사용 중지합니다. (일단 사용 설정하면 사용 중지하지 않는 것이 좋습니다.)

비동기 기능을 사용 설정 또는 중지합니다.

지연된 타이머 이벤트 및 XHR 응답 캡처

Gmail에서 다음과 같은 메시지를 본 적이 있을 것입니다.

Gmail에서 이메일 전송을 다시 시도합니다.

요청을 전송하는 데 문제가 있는 경우 (서버에 문제가 있거나 클라이언트 측에 네트워크 연결 문제가 있는 경우) Gmail은 잠시 후 자동으로 메일을 다시 전송하려고 시도합니다.

비동기 호출 스택이 지연된 타이머 이벤트와 XHR 응답을 분석하는 데 어떻게 도움이 되는지 확인하기 위해 예시 Gmail을 사용하여 이 흐름을 다시 만들었습니다. 전체 JavaScript 코드는 위 링크에서 확인할 수 있지만 흐름은 다음과 같습니다.

Gmail 예시의 플로우 차트
위 다이어그램에서 파란색으로 강조 표시된 메서드는 비동기식으로 작동하므로 이 새로운 DevTool 기능이 가장 유용하게 사용될 수 있는 위치입니다.

이전 버전의 DevTools에서 호출 스택 패널만 보면 postOnFail() 내의 브레이크포인트로 postOnFail()가 호출된 위치에 관한 정보를 거의 얻을 수 없습니다. 그러나 비동기 스택을 사용 설정할 때의 차이점을 살펴보세요.

이전
비동기 호출 스택이 없는 모의 Gmail 예시에서 설정된 중단점
비동기가 사용 설정되지 않은 호출 스택 패널입니다.

여기에서 postOnFail()가 AJAX 콜백에서 시작되었지만 추가 정보는 없는 것을 확인할 수 있습니다.

비동기 호출 스택이 있는 가상 Gmail 예시에서 설정된 중단점
비동기가 사용 설정된 호출 스택 패널

여기에서 XHR이 submitHandler()에서 시작된 것을 확인할 수 있습니다. 좋은 소식입니다.

비동기 호출 스택을 사용 설정하면 전체 호출 스택을 확인하여 요청이 submitHandler() (제출 버튼을 클릭한 후에 발생)에서 시작되었는지 또는 retrySubmit() (setTimeout() 지연 후에 발생)에서 시작되었는지 쉽게 확인할 수 있습니다.

submitHandler()
비동기 호출 스택이 있는 가상 Gmail 예시에서 설정된 중단점
retrySubmit()
비동기 호출 스택이 있는 가상 Gmail 예시에서 설정된 다른 중단점

비동기식 감시 표현식

전체 호출 스택을 탐색하면 관찰 표현식도 업데이트되어 그때의 상태를 반영합니다.

비동기 호출 스택에서 보기 표현식을 사용하는 예

이전 범위의 코드 평가

표현식을 단순히 감시하는 것 외에도 DevTools JavaScript 콘솔 패널에서 바로 이전 범위의 코드와 상호작용할 수 있습니다.

Dr. Who라고 가정해 보겠습니다. 타디스 안에 들어가기 전의 시계와 '지금'의 시계를 비교하는 데 도움이 필요합니다. DevTools 콘솔에서 여러 실행 지점의 값을 쉽게 평가, 저장, 계산할 수 있습니다.

비동기 호출 스택과 함께 JavaScript 콘솔을 사용하는 예시입니다.
JavaScript 콘솔을 비동기 호출 스택과 함께 사용하여 코드를 디버그합니다. 위의 데모는 여기에서 확인할 수 있습니다.

DevTools에서 표현식을 조작하면 소스 코드로 다시 전환하고 수정한 후 브라우저를 새로고침하는 데 드는 시간을 절약할 수 있습니다.

체이닝된 약속 확인 해제

이전의 Gmail 모의 흐름은 비동기 호출 스택 기능을 사용 설정하지 않으면 파악하기 어렵다고 생각했다면 체이닝된 약속과 같이 더 복잡한 비동기 흐름에서는 얼마나 더 어려울지 상상해 보세요. 제이크 아치볼드의 JavaScript Promises 튜토리얼의 마지막 예시를 다시 살펴보겠습니다.

다음은 Jake의 async-best-example.html 예에서 호출 스택을 탐색하는 애니메이션입니다.

이전
비동기 호출 스택이 없는 약속 예시에서 설정된 중단점
비동기가 사용 설정되지 않은 호출 스택 패널입니다.

약속을 디버그하려고 할 때 호출 스택 패널에 정보가 거의 표시되지 않는 것을 볼 수 있습니다.

비동기 호출 스택이 있는 promises 예에서 설정된 브레이크포인트
비동기가 사용 설정된 호출 스택 패널

와우! 이러한 약속 콜백이 많습니다.

웹 애니메이션에 대한 유용한 정보 얻기

HTML5Rocks 보관 파일을 자세히 살펴보겠습니다. 폴 루이스의 requestAnimationFrame을 사용한 더 가볍고 강력하며 빠른 애니메이션을 기억하시나요?

requestAnimationFrame 데모를 열고 post.html의 update() 메서드 시작 부분 (874번 줄 주변)에 중단점을 추가합니다. 비동기 호출 스택을 사용하면 스크롤 이벤트 콜백을 시작하는 지점까지 다시 이동하는 기능을 비롯하여 requestAnimationFrame에 대한 더 많은 통계를 얻을 수 있습니다.

이전
비동기 호출 스택이 없는 requestAnimationFrame 예시에 설정된 중단점
비동기가 사용 설정되지 않은 호출 스택 패널입니다.
비동기 호출 스택이 있는 requestAnimationFrame 예시에 설정된 중단점
비동기 사용 설정

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

MutationObserver를 사용하면 DOM의 변경사항을 관찰할 수 있습니다. 이 간단한 예에서는 버튼을 클릭하면 새 DOM 노드가 <div class="rows"></div>에 추가됩니다.

demo.html의 nodeAdded() (31번 줄) 내에 중단점을 추가합니다. 비동기 호출 스택을 사용 설정하면 이제 addNode()를 통해 호출 스택을 초기 클릭 이벤트로 다시 이동할 수 있습니다.

이전
비동기 호출 스택이 없는 mutationObserver 예시에서 설정된 중단점
비동기가 사용 설정되지 않은 호출 스택 패널입니다.
비동기 호출 스택이 있는 mutationObserver 예시에서 설정된 중단점
비동기 사용 설정

비동기 호출 스택에서 JavaScript를 디버깅하기 위한 도움말

함수 이름 지정

모든 콜백을 익명 함수로 할당하는 경향이 있다면 호출 스택을 더 쉽게 볼 수 있도록 이름을 지정하는 것이 좋습니다.

예를 들어 다음과 같은 익명 함수를 생각해 보겠습니다.

window.addEventListener('load', function() {
  // do something
});

이름을 windowLoaded()와 같이 지정합니다.

window.addEventListener('load', function <strong>windowLoaded</strong>(){
  // do something
});

로드 이벤트가 실행되면 DevTools 스택 트레이스에 난해한 '(익명 함수)' 대신 함수 이름이 표시됩니다. 이렇게 하면 스택 트레이스에서 어떤 일이 일어나고 있는지 한눈에 쉽게 확인할 수 있습니다.

이전
익명 함수
이름이 지정된 함수

더 살펴보기

요약하면 다음은 DevTools에서 전체 호출 스택을 표시하는 모든 비동기 콜백입니다.

  • 타이머: setTimeout() 또는 setInterval()가 초기화된 위치로 돌아갑니다.
  • XHR: xhr.send()가 호출된 위치로 돌아갑니다.
  • 애니메이션 프레임: requestAnimationFrame가 호출된 위치로 돌아갑니다.
  • Promise: 프로미스가 해결된 위치로 돌아갑니다.
  • Object.observe: 관찰자 콜백이 원래 바인딩된 위치로 돌아갑니다.
  • MutationObservers: 변형 관찰자 이벤트가 실행된 위치로 돌아갑니다.
  • window.postMessage(): 프로세스 내 메시지 호출을 살펴봅니다.
  • DataTransferItem.getAsString()
  • FileSystem API
  • IndexedDB
  • WebSQL
  • addEventListener()를 통한 대상 DOM 이벤트: 이벤트가 실행된 위치로 돌아갑니다. 성능상의 이유로 일부 DOM 이벤트는 비동기 호출 스택 기능을 사용할 수 없습니다. 현재 사용 가능한 이벤트의 예로는 'scroll', 'hashchange', 'selectionchange'가 있습니다.
  • addEventListener()를 통한 멀티미디어 이벤트: 이벤트가 발생한 위치로 돌아갑니다. 사용 가능한 멀티미디어 이벤트에는 오디오 및 동영상 이벤트 (예: 'play', 'pause', 'ratechange'), WebRTC MediaStreamTrackList 이벤트 (예: 'addtrack', 'removetrack'), MediaSource 이벤트 (예: 'sourceopen')가 있습니다.

JavaScript 콜백의 전체 스택 트레이스를 볼 수 있으면 머리카락이 빠지지 않습니다. DevTools의 이 기능은 여러 비동기 이벤트가 서로 관련하여 발생하거나 비동기 콜백 내에서 포착되지 않은 예외가 발생하는 경우에 특히 유용합니다.

Chrome에서 사용해 보세요. 이 새로운 기능에 관한 의견이 있으면 Chrome DevTools 버그 추적기 또는 Chrome DevTools 그룹에 알려주세요.