• 주의 1. 이 글은 async / await와 아무런 관련이 없습니다.
  • 주의 2. 이 글에는 멍충함이 묻어있을 수 있습니다.
  • 주의 3. 테스트 방법이 잘못됐을 수 있습니다.
  • 주의 4. 위의 이유로 예고없이 삭제될 수 있습니다.
  • 주의 5. 수정할 사항이 있다면 댓글에 남겨주세요. 검증 후 본문을 업데이트 하겠습니다.

 

의심

큰 프로젝트를 앞두고 PoC를 진행하다 기본적인 부분을 돌아보는 시간을 갖게 되었습니다. 어플리케이션에서 전반적으로 사용하는 기능을 개발해야하는데 평소에 알고있다 생각했던 로딩과 실행에 관한 기본적인 것들을 확인하던 중, 제가 알고있던 사실을, 제가 검증한 적이 없다는 것을 깨닫고 스펙을 확인한 후 나름의 환경을 구성하여 테스트를 진행했습니다.

script의 async / defer 요소

제가 검증하려던 부분은 스크립트의 로딩과 실행이었습니다. 모듈, 의존되는 기능 실행, DOM 핸들링에 관하여 테스트를 실행하다 보니 자연스럽게 async와 defer를 테스트하게 되었습니다. 우선 script의 async와 defer는 무엇인지, 어떤 특징과 차이를 가지는지 알아보겠습니다.

 

출처 / MDN

 

async | HTML5

For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available.
For module scripts, if the async attribute is present then the scripts and all their dependencies will be executed in the defer queue, therefore they will get fetched in parallel to parsing and evaluated as soon as they are available.
This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and evaluate scripts before continuing to parse. defer has a similar effect in this case.
This is a boolean attribute: the presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
See Browser compatibility for notes on browser support. See also Async scripts for asm.js.

 

defer

This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.
Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.
This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.
The defer attribute has no effect on module scripts — they defer by default.
Scripts with the defer attribute will execute in the order in which they appear in the document.
This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and evaluate scripts before continuing to parse. async has a similar effect in this case.

(*한글 번역이 매끄럽지 않아 영문으로 가져왔습니다.)

 

MDN과 '노란책'으로 통용되는 '프론트엔드 개발자를 위한 자바스크립트 프로그래밍'에서 async와 defer에 관하여 언급한 내용을 요약하면 async는 HTML5에 등장, 즉시 다운로드 후 사용이 가능한 시점에 바로 실행, defer는 HTML 4.01에서 소개, 즉시 다운로드하지만 문서가 준비되었을 때 실행한다 입니다.

 

출처 / async vs defer attributes

 

 

 

위의 설명과 이미지를 보면 스크립트의 async와 defer 다운로드와 실행 순서는 async는 다운로드 된 이후, 다큐먼트가 준비된 것과는 별개로 실행 가능한 시점에 바로 실행이 될것이고 defer는 다운로드 후 문서의 콘텐트가 모두 완료된 후, DOMContentLoaded 전에 실행되리라 짐작할 수 있습니다. 그런데 꼭 그렇지만은 않았습니다.

환경 구축

예제파일

 

위의 링크에서 예제파일을 다운로드 및 설치 후 npm run start를 입력하시면 간단한 테스트베드에서 실제로 동작하는 모습을 콘솔창을 통해 확인할 수 있습니다. index.html을 확인하면 head의 script 구문들과 body 안에서 inline으로 작동하는 스크립트 확인이 가능하며, head의 script 작성 순서는 다음과 같습니다.

 

  • index.js / global function definition
  • async.js / async option
  • defer.js / defer option
  • default / no options

 

우리가 앞서 살펴본 내용으로 추론하자면,

1) index.js는 다큐먼트 구문 분석을 멈추고 실행

2) async.js는 다운로드 즉시 실행되므로 async.js에서 정의한 색상(red)의 div는 페이지에서 확인이 불가

3) defer.js는 다큐먼트가 준비된 후 실행되므로 마지막에 실행

4) default.js는 문서 작업을 멈춘 후 바로 실행될 것이므로 async.js와 동일하게 페이지에서 확인이 불가

 

할 것으로 예상했습니다.

검증

파이어폭스, 사파리, 크롬으로 해당 테스트를 진행했습니다. (IE는 여건상 할 수 없었습니다.) 아래는 테스트를 진행한 결과의 스크린샷 입니다.

 

 

예상했던 대로 테스트 결과, 브라우저 별로 작동하는 방식이 달랐습니다. 하지만 너무나 다릅니다. index.js를 제외한 스크립트의 실행 순서는 다음과 같았습니다.

 

파이어폭스
ASYNC -> DEFAULT -> INLINE / head -> INLINE / body -> INLINE / after header -> INLINE / after section -> INLINE / after footer -> DEFER -> DOMContentLoaded -> load

 

사파리

DEFAULT -> INLINE / head -> INLINE / body –> INLINE / after header -> INLINE / after section –> INLINE / after footer -> DEFER -> DOMContentLoaded –> ASYNC -> load

 

크롬

DEFAULT -> INLINE / head -> INLINE / body -> INLINE / after header -> INLINE / after section -> INLINE / after footer -> ASYNC -> DEFER -> DOMContentLoaded -> load

 

추가적으로 모든 브라우저에서 계속 동일한 결과를 주지는 않았습니다. 가끔은 실행 순서가 바뀌고 캐시를 삭제하지 않은 페이지 재진입은 크롬에서 꽤 높은 확률로 async가 defer 보다 뒤에서 실행되었습니다. 이것은 단어를 어떻게 이해했느냐와 관련된 문제일 수도 있겠지만 너무나 당연시 여기던 저의 '상식'이 잘못되었음을 깨닫게 되었습니다. 여기서 확실히 다짐한 점은, '절대 스크립트 로딩과 실행을 option 값으로만(async, defer) 판단하고 사용하지 말자'입니다.

결론

'알고있는 내용이 알고있던 것과 다를 수 있음을 기억하자'

 

파이어폭스 조차 페이지 리로드를 계속해서 하다보면 '빨간 박스'가 페이지에 노출될 때가 많습니다. 물론 스크립트 파일을 내려받고 실행하는 것이 동일한 기능을 한다해도 늘 같은 속도를 보장하지는 않겠지만 이를 의식하지 않고 암기한 내용을 사용하면 큰 낭패를 볼 가능성이 보입니다. 해당 문제에 관한 내용은 추후 포스팅을 통해서 그 이유를 추적하고자 합니다. (물론, 언제할지는 미정입니다만...)

 

무엇이든 신뢰하지 말자는게 아니라 확실히 알고 사용해야 한다에 초점을 두고 알고있던 기능도 한번쯤은 이렇게 테스트 해보는 계기가 되었으면 합니다.

+ Recent posts