ทำความเข้าใจ async และ defer กับการโหลด javascript

ทุกวันนี้ถึงแม้จะมี HTML5 ที่มาพร้อมกับ CSS3 ที่ทำให้การพัฒนาเว็บเว็บไซต์ลดการใช้ javascript ลงได้ในบ้างจุด แต่ในบ้างฟังก์ชัน หรือลูกเล่นบางอย่างก็ยังต้องปล่อยให้เป็นหน้าที่ของ javascript อยู่ และแน่นอนว่า เว็บไซต์ที่มีการใช้งาน javascript เยอะๆ จะต้องมีการพิจารณาลึกลงไปจนถึงโครงสร้างในการวาง javascript และ CSS และการเรียงลำดับในการโหลด resource ต่างๆ

ในบทความนี้ผมเลยจะมาเล่าเกี่ยวกับการจัดวางและการโหลด javascript เพื่อให้เพื่อนๆ จะได้นำไปใช้ได้อย่างเหมาะสมกับเว็บไซต์ของตัวเองครับ ก่อนอื่นก็ดูเลยว่าวิธีการวาง javascript แบบเดิมๆ

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

จากโค้ดข้างบน javascript ทั้งสองไฟล์จะถูกโหลดไปพร้อมๆ กัน (parallel) พอโหลดเสร็จก็จะถูก execute ตามลำดับเลยทันที อ่ะงงล่ะสิ ก็หมายความว่าไฟล์ 2.js จะถูก execute หลังจากที่ 1.js จะถูก execute เสร็จหรือล้มเหลว (fail) ไป ในขณะเดียวกันไฟล์ 1.js ก็จะไม่ถูก execute จนกว่า javascript หรือ css ที่อยู่ก่อนหน้าจะถูก execute เสร็จก่อน

ที่สำคัญ browser จะไม่มีการ render อะไรจนกว่าเหตุการณ์ข้างบนจะเสร็จสิ้น หรือพูดง่ายๆ คือเราจะเห็นหน้าเว็บขาวๆ หรือค้างอยู่พร้อมกับ browser หมุนติ้วๆ ที่เป็นอย่างนี้ก็เพราะ DOM APIs ในยุคแรกๆ ยอมให้พ่น string เข้าไปใน html ได้ โดยการใช้ document.write ทำให้ต้องหยุดการ render ไว้ก่อน แต่ browser รุ่นใหม่ๆ ก็ดีขึ้นมาหน่อยคือ จะยัง scan หรือ parse document อยู่เบื้องหลัง (ไม่รู้จะใช้คำว่าอะไร background ^^”) และมี trigger ให้การโหลด resource อื่นๆ (js, images, css ) แต่ก็ยังไม่การ block  ไม่ให้มีการ render ต่ออยู่ดี

นี่ก็เป็นเห็นผลที่เราต้องวาง javascript ไว้ท้ายสุดเท่าที่จะเป็นไปได้ แต่ก็ใช่ว่าจะราบรื่นเพราะกว่าจะมีการโหลดและ execute javascript ที่วางไว้ท้ายสุดก็ต้องรอจนกว่าจะโหลด resource อื่นๆ ที่ browser เจอก่อนหน้าให้เสร็จก่อน

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

Microsoft เห็นปัญหานี้ก็เลยแนะนำ defer มาพร้อมกับ Internet Exploer 4 เมื่อมีการเพื่อ defer เข้าไปที่แท็ก javascript จะทำให้ javascript ถูก execute ตามลำดับที่มีการวาง script ก่อนที่จะสั่งให้ event DOMContentLoaded ทำงาน

ดูเหมือนจะราบรื่น แต่ก็ยังมีปัญหานิดหน่อย คือ ถ้ามีการเรียกใช้ฟังก์ชันหรือ operations ของ DOM จะทำให้ script นั้นหยุดการทำงาน (execution) และข้ามไป execute script ถัดไป

1.js

console.log('1');
document.getElementsByTagName('p')[0].innerHTML = 'Changing some content';
console.log('2');

2.js

console.log('3');

ถ้า script ถูก execute  ตามลำดับผลที่ได้ก็จะเป็น 1, 2, 3 แต่ browser IE ที่ต่ำกว่าเวอร์ชัน 9 ให้ผลเป็น 1, 3, 2 แทน ถึงจุดนี้ IE 10  และ browser อื่นๆ ก็มีการแก้ไขปัญหาโดยการยืดเวลา  (delay) จนกว่าจะมีการโหลด document เสร็จและ parse เรียบร้อยก่อน javascript จึงจะถูก execute ซึ่งก็เพียงพอถ้ารอได้ ^^ แต่ถ้าต้องการ performance ที่ดีกว่านี้ล่ะจะทำยังไง

<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

ก็มาถึงยุคของ HTML 5 ที่ได้เพิ่ม async เข้ามายังแท็ก script แล้ว async มีผลกับ javascript ยังไง ผลก็คือ script จะถูกโหลดพร้อมๆ กัน (parallel) และจะถูก execute ทันทีที่โหลดเสร็จ มาถึงจุดนี้ 2.js อาจจะถูก execute ก่อน 1.js ก็ได้ ถ้า 2 script ไม่ได้เกี่ยวข้องอะไรกัน (independent) ก็ไม่มีปัญหา แต่ถ้าเกี่ยวข้องกันล่ะก็ หุหุ  เกิด error ขึ้นแน่ครับ ^^

หวังว่าจะมีประโยชน์กับเพื่อนๆ และนำ defer กับ async ไปใช้ให้ถูกวิธีนะครับ ^^

ขอบคุณแหล่งข้อมูลดีๆ ครับ: Deep dive into the murky waters of script loading