การดีบัก JavaScript ที่ไม่พร้อมกันด้วยเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome

บทนำ

ฟีเจอร์ที่มีประสิทธิภาพซึ่งทำให้ JavaScript โดดเด่นคือความสามารถในการทํางานแบบไม่สอดคล้องกันผ่านฟังก์ชัน Callback การกำหนดการเรียกกลับแบบอะซิงโครนัสช่วยให้คุณเขียนโค้ดที่ทำงานตามเหตุการณ์ได้ แต่ก็ทำให้การติดตามข้อบกพร่องเป็นเรื่องยากขึ้นด้วย เนื่องจาก JavaScript ไม่ได้ทำงานแบบเป็นเส้นตรง

แต่ตอนนี้คุณดูกองคําเรียกทั้งหมดของคอลแบ็ก JavaScript แบบไม่สอดคล้องกันได้ใน Chrome DevTools

ภาพรวมคร่าวๆ เกี่ยวกับกองคิวการเรียกแบบไม่พร้อมกัน
ภาพรวมคร่าวๆ เกี่ยวกับกองคิวการเรียกใช้แบบไม่พร้อมกัน (เราจะอธิบายขั้นตอนของเดโมนี้ในเร็วๆ นี้)

เมื่อเปิดใช้ฟีเจอร์สแต็กการเรียกแบบแอซิงค์ในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์แล้ว คุณจะเจาะลึกสถานะของเว็บแอป ณ เวลาต่างๆ ได้ แสดงสแต็กเทรซแบบเต็มสําหรับโปรแกรมรับฟังเหตุการณ์บางรายการ, setInterval,setTimeout, XMLHttpRequest, สัญญา requestAnimationFrame, MutationObservers และอื่นๆ

ขณะดูสแต็กเทรซ คุณสามารถวิเคราะห์ค่าของตัวแปร ณ จุดใดก็ได้ของรันไทม์ ฟีเจอร์นี้เปรียบเสมือนไทม์แมชชีนสำหรับการแสดงออกของใบหน้า

มาเปิดใช้ฟีเจอร์นี้และดูสถานการณ์จำลองเหล่านี้กัน

เปิดใช้การแก้ไขข้อบกพร่องแบบไม่พร้อมกันใน Chrome

ลองใช้ฟีเจอร์ใหม่นี้โดยเปิดใช้ใน Chrome ไปที่แผงแหล่งที่มาของเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome Canary

ถัดจากแผง Call Stack ทางด้านขวามือจะมีช่องทําเครื่องหมายใหม่สําหรับ "Async" สลับช่องทำเครื่องหมายเพื่อเปิดหรือปิดการแก้ไขข้อบกพร่องแบบแอซิงค์ (แต่เมื่อเปิดแล้ว คุณอาจไม่ต้องการปิดอีกเลย)

เปิดหรือปิดฟีเจอร์แบบไม่พร้อมกัน

บันทึกเหตุการณ์ตัวจับเวลาแบบล่าช้าและการตอบกลับ XHR

คุณอาจเคยเห็นสิ่งนี้ใน Gmail มาก่อน

Gmail กำลังพยายามส่งอีเมลอีกครั้ง

หากมีปัญหาในการส่งคำขอ (เซิร์ฟเวอร์มีปัญหาหรือมีปัญหาการเชื่อมต่อเครือข่ายในฝั่งไคลเอ็นต์) Gmail จะพยายามส่งข้อความอีกครั้งโดยอัตโนมัติหลังจากหมดเวลาสั้นๆ

หากต้องการดูว่าสแต็กการเรียกแบบแอซิงค์ช่วยเราวิเคราะห์เหตุการณ์ตัวจับเวลาแบบเลื่อนเวลาและคำตอบ XHR ได้อย่างไร เราจึงได้สร้างขั้นตอนดังกล่าวอีกครั้งโดยใช้ตัวอย่างจำลอง Gmail ดูโค้ด JavaScript ฉบับเต็มได้ในลิงก์ด้านบน แต่ขั้นตอนมีดังนี้

แผนภาพขั้นตอนของตัวอย่าง Gmail จำลอง
ในแผนภาพด้านบน เมธอดที่ไฮไลต์ด้วยสีน้ำเงินเป็นตำแหน่งหลักที่ฟีเจอร์ DevTool ใหม่นี้ให้ประโยชน์สูงสุด เนื่องจากเมธอดเหล่านี้ทำงานแบบไม่พร้อมกัน

เมื่อดูที่แผงสแต็กการเรียกใช้ใน DevTools เวอร์ชันเก่าๆ เพียงอย่างเดียว จุดหยุดพักภายใน postOnFail() จะให้ข้อมูลเพียงเล็กน้อยเกี่ยวกับตำแหน่งที่มีการเรียกใช้ postOnFail() แต่ให้ดูความแตกต่างเมื่อเปิดใช้กองแบบไม่พร้อมกัน

ก่อน
จุดหยุดที่ตั้งไว้ในตัวอย่าง Gmail จําลองที่ไม่มีสแต็กการเรียกแบบแอสซิงค์
แผงสแต็กการเรียกโดยไม่เปิดใช้การทำงานแบบไม่พร้อมกัน

คุณจะเห็นว่า postOnFail() เริ่มต้นจาก AJAX Callback แต่ไม่มีข้อมูลเพิ่มเติม

หลังจาก
จุดหยุดที่ตั้งไว้ในตัวอย่าง Gmail จําลองที่มีสแต็กการเรียกแบบแอสซิงค์
แผงสแต็กการเรียกใช้ที่เปิดใช้การทำงานแบบไม่พร้อมกัน

คุณจะเห็นว่า XHR เริ่มต้นจาก submitHandler() ดีมาก

เมื่อเปิดสแต็กการเรียกแบบแอซิงค์ คุณจะดูสแต็กการเรียกทั้งหมดเพื่อดูว่าคำขอเริ่มต้นจาก submitHandler() (ซึ่งเกิดขึ้นหลังจากคลิกปุ่ม "ส่ง") หรือจาก retrySubmit() (ซึ่งเกิดขึ้นหลังจากรอ setTimeout() วินาที) ได้โดยง่าย

submitHandler()
การตั้งจุดหยุดพักในตัวอย่าง Gmail จําลองที่มีสแต็กการเรียกแบบแอสซิงค์
retrySubmit()
จุดพักอีกจุดที่ตั้งไว้ในตัวอย่าง Gmail จําลองที่มีสแต็กการเรียกแบบแอสซิงค์

ดูนิพจน์การดูแบบไม่พร้อมกัน

เมื่อคุณเดินผ่านกองซ้อนการเรียกทั้งหมด นิพจน์ที่ดูอยู่จะอัปเดตเพื่อแสดงสถานะ ณ ขณะนั้นด้วย

ตัวอย่างการใช้นิพจน์การติดตามกับสแต็กการเรียกแบบแอสซิงค์

ประเมินโค้ดจากขอบเขตที่ผ่านมา

นอกจากการดูนิพจน์แล้ว คุณยังโต้ตอบกับโค้ดจากขอบเขตก่อนหน้าได้ในแผงคอนโซล JavaScript ของเครื่องมือสำหรับนักพัฒนาเว็บ

ลองจินตนาการว่าคุณเป็น Dr. Who และต้องการความช่วยเหลือเล็กน้อยในการเปรียบเทียบนาฬิกาจากก่อนขึ้น Tardis กับ "ตอนนี้" จากคอนโซล DevTools คุณสามารถประเมิน จัดเก็บ และทําการคํานวณค่าจากจุดดำเนินการต่างๆ ได้อย่างง่ายดาย

ตัวอย่างการใช้คอนโซล JavaScript กับสแต็กการเรียกแบบแอซิงค์
ใช้คอนโซล JavaScript ร่วมกับสแต็กการเรียกแบบแอซิงโครนัสเพื่อแก้ไขข้อบกพร่องของโค้ด ดูการสาธิตข้างต้นได้ที่นี่

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

แก้ปัญหาการแก้ไขสัญญาแบบเชน

หากคุณคิดว่าการแยกแยะขั้นตอนจำลองของ Gmail ก่อนหน้านี้เป็นเรื่องยากหากไม่ได้เปิดใช้ฟีเจอร์สแต็กการเรียกแบบแอซิงโครนัส คุณลองนึกภาพว่าขั้นตอนที่ซับซ้อนมากขึ้นแบบแอซิงโครนัส เช่น Promise แบบเชน จะยากขนาดไหน มาดูตัวอย่างสุดท้ายของบทแนะนำเกี่ยวกับสัญญา JavaScript ของ Jake Archibald กัน

ต่อไปนี้คือภาพเคลื่อนไหวสั้นๆ ของการเรียกใช้สแต็กการเรียกในตัวอย่าง async-best-example.html ของ Jake

ก่อน
ตัวอย่างการหยุดพักชั่วคราวในพรอมต์ที่ไม่มีสแต็กการเรียกแบบแอ็กซิงโครนัส
แผงสแต็กการเรียกโดยไม่เปิดใช้การทำงานแบบไม่พร้อมกัน

โปรดสังเกตว่าแผงกองซ้อนการเรียกมีข้อมูลน้อยมากเมื่อพยายามแก้ไขข้อบกพร่องของ Promise

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

ว้าว คำสัญญาดังกล่าว การติดต่อกลับจำนวนมาก

รับข้อมูลเชิงลึกเกี่ยวกับภาพเคลื่อนไหวบนเว็บ

มาดูรายละเอียดเพิ่มเติมจากที่เก็บ HTML5Rocks จำบทความของ Paul Lewis เรื่องภาพเคลื่อนไหวที่เบาลง มีประสิทธิภาพมากขึ้น และเร็วขึ้นด้วย requestAnimationFrame ได้ไหม

เปิดการสาธิต requestAnimationFrame และเพิ่มจุดหยุดพักที่จุดเริ่มต้นของเมธอด update() (ประมาณบรรทัด 874) ของ post.html สแต็กการเรียกแบบแอซิงค์ช่วยให้เราได้รับข้อมูลเชิงลึกเพิ่มเติมเกี่ยวกับ requestAnimationFrame รวมถึงสามารถย้อนกลับไปยังการเรียกกลับเหตุการณ์การเลื่อนที่เริ่มต้น

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

ติดตามการอัปเดต DOM เมื่อใช้ MutationObserver

MutationObserver ช่วยให้เราสังเกตการเปลี่ยนแปลงใน DOM ได้ ในตัวอย่างง่ายๆ นี้ เมื่อคุณคลิกปุ่ม ระบบจะเพิ่มโหนด DOM ใหม่ต่อท้าย <div class="rows"></div>

เพิ่มเบรกพอยต์ภายใน nodeAdded() (บรรทัด 31) ใน demo.html เมื่อเปิดใช้สแต็กการเรียกแบบแอซิงค์แล้ว ตอนนี้คุณสามารถเดินสแต็กการเรียกกลับผ่าน addNode() ไปยังเหตุการณ์การคลิกเริ่มต้นได้

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

เคล็ดลับในการแก้ไขข้อบกพร่องของ JavaScript ในกองคําเรียกแบบไม่พร้อมกัน

ตั้งชื่อฟังก์ชัน

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

เช่น ฟังก์ชันที่ไม่ระบุชื่อเช่นนี้

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

และตั้งชื่อให้ เช่น windowLoaded()

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

เมื่อเหตุการณ์การโหลดเริ่มทํางาน เหตุการณ์ดังกล่าวจะปรากฏในสแต็กเทรซของ DevTools พร้อมชื่อฟังก์ชันแทน "(ฟังก์ชันที่ไม่ระบุชื่อ)" ที่เป็นรหัส ซึ่งจะช่วยให้คุณเห็นสิ่งที่เกิดขึ้นในสแต็กเทรซได้อย่างรวดเร็ว

ก่อน
ฟังก์ชันนิรนาม
หลัง
ฟังก์ชันที่มีชื่อ

สำรวจเพิ่มเติม

โดยสรุปแล้ว รายการต่อไปนี้คือคอลแบ็กแบบแอซิงโครนัสทั้งหมดที่เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะแสดงสแต็กการเรียกทั้งหมด

  • ตัวจับเวลา: เดินกลับไปที่จุดเริ่มต้น setTimeout() หรือ setInterval()
  • XHR: กลับไปที่ตำแหน่งที่เรียก xhr.send()
  • เฟรมภาพเคลื่อนไหว: เดินกลับไปที่เรียก requestAnimationFrame
  • Promises: กลับไปที่ Promise ที่ได้รับการแก้ไขแล้ว
  • Object.observe: กลับไปที่ตำแหน่งที่ผูกการเรียกกลับของออบเซอร์เวอร์ไว้ตั้งแต่แรก
  • MutationObservers: กลับไปที่จุดที่เหตุการณ์ MutationObserver เริ่มทํางาน
  • window.postMessage(): เรียกใช้การเรียกใช้การรับส่งข้อความภายในกระบวนการ
  • DataTransferItem.getAsString()
  • FileSystem API
  • IndexedDB
  • WebSQL
  • เหตุการณ์ DOM ที่มีสิทธิ์ผ่าน addEventListener(): กลับไปที่จุดที่เรียกเหตุการณ์ให้แสดง กิจกรรม DOM บางรายการไม่มีสิทธิ์ใช้ฟีเจอร์สแต็กการเรียกแบบแอซิงค์เนื่องจากเหตุผลด้านประสิทธิภาพ ตัวอย่างเหตุการณ์ที่ใช้ได้ในปัจจุบัน ได้แก่ "scroll", "hashchange" และ "selectionchange"
  • เหตุการณ์มัลติมีเดียผ่าน addEventListener(): ย้อนกลับไปยังจุดที่เหตุการณ์เริ่มทํางาน เหตุการณ์มัลติมีเดียที่ใช้ได้มีดังนี้ เหตุการณ์เสียงและวิดีโอ (เช่น "play", "pause", "ratechange"), เหตุการณ์ MediaStreamTrackList ของ WebRTC (เช่น "addtrack", "removetrack") และเหตุการณ์ MediaSource (เช่น "sourceopen")

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

ลองใช้ใน Chrome หากมีความคิดเห็นเกี่ยวกับฟีเจอร์ใหม่นี้ โปรดส่งอีเมลถึงเราในเครื่องมือติดตามข้อบกพร่องของเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome หรือในกลุ่มเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome