Programming/💥 뽀개기

[브라우저, HTML] JavaScript 파일 비동기 처리(defer, async)

남남이루 2024. 4. 17. 17:39

Intro

풀스택 인턴으로 근무하며 Web Frontend 개발을 처음 했을 때, 도대체가 JavaScript 파일에서 DOM을 조작하는 코드가 HTML의 스크립트 태그 선언 위치에 따라 언제는 페이지에 반영이 되고, 언제는 안 되는 오류를 겪은 적이 있다. 당시 (본인 제외 유일한 개발자였던) 백엔드 사수는 "스크립트 태그 위치 바꾸면 되던데요?"라고 해주셔서 해결은 했으나, 당시에는 그 이유를 명확히 밝히지 못했었다.

웹 사이트의 디자인을 맡는 것이 CSS, 구조를 맡는 것이 HTML이라면, 살아 숨쉬는 Action을 담당하는 것이 JavaScript라고 비유하곤 한다. 이 때 JavaScript가 서버와 데이터를 왔다리 갔다리 하기 위해서는 JS파일의 비동기 처리를 확실하게 이해해야 한다. "JavaScript 비동기 처리"를 키워드를 검색할 때 마다 거슬리던 defer, async 속성은 마치... 금붕어똥 같았다.

따라서 이참에 defer, async언제, 왜 쓰는 지를 파헤쳐 보고 둘의 공통점과 차이점까지 살펴보겠다.

조건

1. head > script  > body
2. 해당 JavaScript 파일은 body 태그 내부의 div DOM을 조작해서 텍스트를 추가하는 동작을 한다.
console.log("js file loaded");
const Div = document.querySelector("#edit-by-js");
Div.textContent = "JS FILE LOAD SUCCESS";

 

비교

case 1. JavaScript 파일을 script 태그로 상단에서 로드해온다.

case 2. script 태그의 위치를 HTML 문서 가장 하단으로 내린다.

case 3. defer 속성을 사용한다.


 

| case 1. JavaScript 파일을 script 태그로 상단에서 로드해온다.

<!DOCTYPE html>
<html>

<head>
  <script src="./index.js"></script>
</head>

<body>
  <H1>THIS IS TO TEST DEFER</H1>
  <div id="edit-by-js"></div>
</body>
</html>

가장 기본적인 상황이다. script 태그는 html 돔을 그리기 전에 JS 파일을 로드해오기 때문에 JS파일 내부에서 DOM에 접근해 조작할 수 없습니다. 따라서 "Can not set properties of null" 에러를 마주하게 됩니다.

이처럼 DOM을 미처 다 그리기 전에 JS의 동작이 DOM에 접근해서 조작하는 내용이 있다면 오류가 발생합니다. (defer 사용 이유1)

case1 상황에 발생하는 오류

 

| case 2. script 태그의 위치를 HTML 문서 가장 하단으로 내린다.

<!DOCTYPE html>
<html>

<body>
  <H1>THIS IS TO TEST DEFER</H1>
  <div id="edit-by-js"></div>
</body>
<script src="./index.js"></script>

</html>

- body > script

성공적으로 파일을 로드하고 DOM에 접근할 수 있다. 하지만 HTML 파일이 너무 무거운 경우, 스크립트가 HTML을 전부 기다려야 하기 때문에 페이지가 느릴 수 있어 문제가 됩니다.

 

| case 3. defer 속성을 사용한다.

브라우저는 defer 속성이 있는 스크립트(이하 defer 스크립트 또는 지연 스크립트)를 '백그라운드’에서 다운로드 합니다. 따라서 지연 스크립트를 다운로드 하는 도중에도 HTML 파싱이 멈추지 않습니다. 그리고 defer 스크립트 실행은 페이지 구성이 끝날 때까지 지연 됩니다.
출처 : https://ko.javascript.info/script-async-defer

 

이처럼 스크립트가 먼저 불러와져서 하단의 DOM에 접근 및 조작이 어려울 때 defer속성을 사용하기도 하고, 혹은 상단의 script가 오래 걸려서 script 실행을 전부 기다리는 동안 화면을 그리지 못해서(DOM) 하단의 페이지가 내용없이 하얗게 뜰 때 defer를 사용할 수 있습니다. (defer 사용 이유2)

defer 속성을 이용해 페이지 생성(html 그리기, html 파싱)을 막지 않고 '백그라운드(브라우저의 엔진 영역 일부)'에서 script를 다운로드합니다(병렬로 처리). 다운로드가 완료되면 페이지의 DOM 구성이 끝날 때까지 script 실행을 지연했다가, 페이지 구성이 끝나면 script를 실행합니다. 

script태그에 defer가 추가된 경우 실행 순서  :
(script 불러오기 시작-백그라운드 병렬) > HTML 파싱 및 구성  >  (script 불러오기 완료) >
HTML 파싱, DOM 생성 완료 > 지연 스크립트 내용 실행 > DOMContentLoaded 이벤트 발생

* 다만, 지연스크립트 실행 중에는 HTML 파싱은 멈추지 않는다.

 

 

| defer와 비슷하지만 다른 async

async는 페이지 콘텐츠 생성, 다른 비동기 스크립트들과 독립적으로 동작합니다. (* 하지만, async 스크립트 실행중에는 HTML 파싱은 멈춘다.) 따라서 페이지 생성 시점과 스크립트 수행 순서를 보장할 수 없기 때문에, 페이지 컨텐츠와 무관한 작업에 대해 async 속성을 사용합니다. 

지연 스크립트(defer)가 실행된 후에 "DOMContentLoaded 이벤트"가 발생하는 것과 달리 비동기 스크립트는 "DOMContentLoaded 이벤트"의 전이나 후 언제 실행될 지 보장할 수 없습니다. 다른 비동기 스크립트들과 독립적으로 실행되기 때문에 async 스크립트끼리도 실행 순서가 보장되지 않습니다.

비동기 스크립트(async)방문자 수 카운터광고 관련 스크립트처럼 각각 독립적인 역할을 하는 서드 파티 스크립트를 현재 개발 중인 스크립트에 통합하려 할 때 아주 유용합니다. async 스크립트는 개발 중인 스크립트에 의존하지 않고, 그 반대도 마찬가지이기 때문입니다. 
출처 : https://ko.javascript.info/script-async-defer
/* google analytics 삽입 예시 */
<script async src="https://google-analytics.com/analytics.js"></script>

요약

defer, async 공통점 :

  • 스크립트 다운로드 시 페이지 렌더링을 막지 않기 때문에 async, defer를 적절히 사용하면 사용자가 오래 기다리지 않고 페이지 콘텐츠를 볼 수 있게 됩니다.

defer, async 차이점

  • defer : defer 속성이 추가된 스크립트는 "지연 스크립트"라고 합니다. 페이지 생성을 기다렸다가 실행되는 특징이 있습니다.
  • async : async 속성이 추가된 스크립트는 "비동기 스크립트"라고 합니다. 페이지 콘텐츠 생성과 독립적으로 수행되고, 콘텐츠 생성 단계와 스크립트 실행 순서를 보장하지 않습니다.

https://ko.javascript.info/script-async-defer

* 참고

https://ko.javascript.info/script-async-defer

 

* DOMContentLoaded : HTML의 DOM 구성이 완료되고, 외부 스타일 시트들은 아직 불러와지지 않은 시점

HTML의 생명주기와 관련 이벤트 : https://ko.javascript.info/onload-ondomcontentloaded

 

DOMContentLoaded, load, beforeunload, unload 이벤트

 

ko.javascript.info