เพิ่มประสิทธิภาพงานที่ใช้เวลานาน

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

งานคืออะไร

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

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

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

เทรดหลักคืออะไร

เทรดหลักคือที่ที่งานส่วนใหญ่ทำงานในเบราว์เซอร์ และเป็นที่ที่มีการเรียกใช้ JavaScript เกือบทั้งหมดที่คุณเขียน

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

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

เพื่อป้องกันปัญหานี้ ให้แบ่งงานที่ใช้เวลานานแต่ละงานออกเป็นงานย่อยๆ ซึ่งแต่ละงานใช้เวลาในการเรียกใช้น้อยลง ซึ่งเรียกว่าการแบ่งงานที่ใช้เวลานาน

งานที่ใช้เวลานานรายการเดียวเทียบกับงานเดียวกันจะแบ่งออกเป็นงานสั้นๆ งานที่ยาวคือสี่เหลี่ยมผืนผ้าใหญ่ 1 รูป และงานแบ่งเป็นชิ้นๆ เป็นช่องเล็กๆ 5 ช่อง ซึ่งมีความยาวรวมกันเท่ากับความยาวของงานนั้น
การแสดงภาพของงานยาว 1 งานเทียบกับงานเดียวกันนั้น โดยจะแบ่งออกเป็น 5 งานที่สั้นกว่า

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

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

กลยุทธ์การจัดการงาน

JavaScript จะถือว่าแต่ละฟังก์ชันเป็นงานเดียว เพราะใช้รูปแบบ run-to-completion ของการเรียกใช้งาน ซึ่งหมายความว่าฟังก์ชันที่เรียกใช้ฟังก์ชันอื่นๆ อีกหลายฟังก์ชันอย่างเช่นตัวอย่างต่อไปนี้ จะต้องทำงานจนกว่าฟังก์ชันที่เรียกทั้งหมดจะเสร็จสมบูรณ์ ซึ่งจะทำให้เบราว์เซอร์ทำงานช้าลง

function saveSettings () { //This is a long task.
  validateForm();
  showSpinner();
  saveToDatabase();
  updateUI();
  sendAnalytics();
}
ฟังก์ชัน SaveSettings ที่แสดงในเครื่องมือสร้างโปรไฟล์ประสิทธิภาพของ Chrome แม้ว่าฟังก์ชันระดับบนสุดจะเรียกฟังก์ชันอื่นๆ อีก 5 รายการ แต่งานทั้งหมดจะเกิดขึ้นในงานที่ใช้เวลานานงานเดียวซึ่งบล็อกเทรดหลัก
saveSettings() ฟังก์ชันเดียวที่เรียกใช้ฟังก์ชัน 5 รายการ งานดังกล่าวต้องทำเป็นส่วนหนึ่งของงานขนาดใหญ่ที่ใช้เวลานาน 1 งาน

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

เลื่อนการเรียกใช้โค้ดด้วยตนเอง

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

function saveSettings () {
  // Do critical work that is user-visible:
  validateForm();
  showSpinner();
  updateUI();

  // Defer work that isn't user-visible to a separate task:
  setTimeout(() => {
    saveToDatabase();
    sendAnalytics();
  }, 0);
}

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

function processData () {
  for (const item of largeDataArray) {
    // Process the individual item here.
  }
}

โชคดีที่มี API อื่นๆ อีก 2-3 รายการที่ช่วยให้คุณเลื่อนการดำเนินการโค้ดไปยังงานในภายหลังได้ เราขอแนะนำให้ใช้ postMessage() เพื่อให้หมดเวลาเร็วขึ้น

นอกจากนี้ คุณยังแบ่งงานโดยใช้ requestIdleCallback() ได้ด้วย แต่ระบบจะกำหนดเวลางานในลำดับความสำคัญต่ำสุดและในช่วงที่ไม่มีเบราว์เซอร์เท่านั้น ซึ่งหมายความว่าหากชุดข้อความหลักไม่ว่างเป็นพิเศษ งานที่กำหนดเวลาด้วย requestIdleCallback() อาจไม่สามารถเรียกใช้ได้

ใช้ async/await เพื่อสร้างคะแนนผลตอบแทน

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

วิธีที่ชัดเจนที่สุดในการดำเนินการนี้คือ Promise ซึ่งแก้ไขปัญหาด้วยการโทรหา setTimeout():

function yieldToMain () {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

ในฟังก์ชัน saveSettings() คุณจะกลับไปที่เทรดหลักหลังจากแต่ละขั้นตอนได้ หากawaitฟังก์ชัน yieldToMain() หลังการเรียกใช้ฟังก์ชันแต่ละครั้ง ซึ่งจะแบ่งงานที่ใช้เวลานานออกเป็นงานหลายงานอย่างมีประสิทธิภาพ ดังนี้

async function saveSettings () {
  // Create an array of functions to run:
  const tasks = [
    validateForm,
    showSpinner,
    saveToDatabase,
    updateUI,
    sendAnalytics
  ]

  // Loop over the tasks:
  while (tasks.length > 0) {
    // Shift the first task off the tasks array:
    const task = tasks.shift();

    // Run the task:
    task();

    // Yield to the main thread:
    await yieldToMain();
  }
}

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

ฟังก์ชัน recordSettings เช่นเดียวกันในเครื่องมือสร้างโปรไฟล์ประสิทธิภาพของ Chrome ตอนนี้สามารถให้ผลตอบแทนได้แล้ว
    ขณะนี้งานดังกล่าวแบ่งออกเป็น 5 งานแยกกัน งานละ 1 งาน
ตอนนี้ฟังก์ชัน saveSettings() จะเรียกใช้ฟังก์ชันย่อยเป็นงานที่แยกจากกัน

API เครื่องจัดตารางเวลาโดยเฉพาะ

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

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

การสนับสนุนเบราว์เซอร์

  • 94
  • 94
  • x

แหล่งที่มา

API เครื่องจัดตารางเวลามีฟังก์ชัน postTask() ซึ่งช่วยให้จัดตารางเวลางานที่ละเอียดขึ้น และช่วยให้เบราว์เซอร์จัดลำดับความสำคัญของงานได้ เพื่อให้งานที่มีลำดับความสำคัญต่ำส่งกลับไปยังเทรดหลัก postTask() ใช้คำสัญญาและยอมรับการตั้งค่า priority

postTask() API มีลำดับความสำคัญ 3 ประการดังนี้

  • 'background' สำหรับงานที่มีลำดับความสำคัญต่ำสุด
  • 'user-visible' สำหรับงานที่มีความสำคัญปานกลาง ตัวเลือกนี้เป็นค่าเริ่มต้นหากไม่ได้ตั้งค่า priority
  • 'user-blocking' สำหรับงานสำคัญที่ต้องเรียกใช้ในลำดับความสำคัญสูง

โค้ดตัวอย่างต่อไปนี้ใช้ API ของ postTask() เพื่อเรียกใช้งาน 3 งานโดยมีลำดับความสำคัญสูงสุดเท่าที่จะเป็นไปได้ และ 2 งานที่เหลือโดยมีลำดับความสำคัญต่ำสุด

function saveSettings () {
  // Validate the form at high priority
  scheduler.postTask(validateForm, {priority: 'user-blocking'});

  // Show the spinner at high priority:
  scheduler.postTask(showSpinner, {priority: 'user-blocking'});

  // Update the database in the background:
  scheduler.postTask(saveToDatabase, {priority: 'background'});

  // Update the user interface at high priority:
  scheduler.postTask(updateUI, {priority: 'user-blocking'});

  // Send analytics data in the background:
  scheduler.postTask(sendAnalytics, {priority: 'background'});
};

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

ฟังก์ชัน SaveSettings ที่แสดงในเครื่องมือสร้างโปรไฟล์ประสิทธิภาพของ Chrome แต่ใช้ postTask โดย postTask จะแยกแต่ละฟังก์ชัน logSettings จะทำงาน และจัดลำดับความสำคัญเพื่อให้การโต้ตอบของผู้ใช้ทำงานได้โดยไม่ถูกบล็อก
เมื่อ saveSettings() ทำงาน ฟังก์ชันจะกำหนดเวลาการเรียกใช้ฟังก์ชันแต่ละรายการโดยใช้ postTask() งานสำคัญที่แสดงต่อผู้ใช้จะถูกกำหนดเวลาโดยมีลำดับความสำคัญสูง ส่วนงานที่ผู้ใช้ไม่รู้มาก่อนจะถูกตั้งเวลาให้ทำงานอยู่เบื้องหลัง วิธีนี้จะช่วยให้การโต้ตอบของผู้ใช้ดำเนินการได้เร็วขึ้น เนื่องจากงานจะแบ่งย่อยและจัดลำดับความสำคัญอย่างเหมาะสม

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

ผลตอบแทนในตัวที่มีการใช้งานอย่างต่อเนื่องโดยใช้ scheduler.yield() API ที่กำลังจะเปิดตัว

ประเด็นสำคัญ: หากต้องการดูคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับ scheduler.yield() ให้อ่านเกี่ยวกับช่วงทดลองใช้จากต้นทาง (ตั้งแต่สรุป) และคำอธิบาย

สิ่งหนึ่งที่เสนอให้เพิ่มใน API เครื่องจัดตารางเวลาคือ scheduler.yield() ซึ่งเป็น API ที่ออกแบบมาเพื่อตอบสนองต่อเทรดหลักในเบราว์เซอร์โดยเฉพาะ การใช้งานจะคล้ายกับฟังก์ชัน yieldToMain() ที่ระบุไว้ข้างต้นในหน้านี้

async function saveSettings () {
  // Create an array of functions to run:
  const tasks = [
    validateForm,
    showSpinner,
    saveToDatabase,
    updateUI,
    sendAnalytics
  ]

  // Loop over the tasks:
  while (tasks.length > 0) {
    // Shift the first task off the tasks array:
    const task = tasks.shift();

    // Run the task:
    task();

    // Yield to the main thread with the scheduler
    // API's own yielding mechanism:
    await scheduler.yield();
  }
}

โดยส่วนใหญ่แล้วโค้ดนี้จะคุ้นเคย แต่แทนที่จะใช้ yieldToMain() โค้ดจะใช้ await scheduler.yield()

แผนภาพ 3 ภาพแสดงงานที่ไม่มีการแสดงผล มีการให้ผลลัพธ์ และให้ผลตอบแทนที่ดีอย่างต่อเนื่อง แต่ไม่ต้องผลตอบแทน ก็ต้องทำงานหนักๆ แทน ผลตอบแทนจะทำให้มีงานต่างๆ ที่สั้นกว่าเดิม แต่งานอื่นๆ ที่ไม่เกี่ยวข้องอาจมาขัดจังหวะได้ เมื่อมีการให้ผลตอบแทนและความต่อเนื่อง ลำดับการดำเนินการของงานที่มีระยะเวลาสั้นลงจะยังคงอยู่
เมื่อคุณใช้ scheduler.yield() การดำเนินการของงานจะทำงานต่อจากจุดที่ค้างไว้แม้ว่าจะถึงจุดผลตอบแทนแล้วก็ตาม

สิทธิประโยชน์ของ scheduler.yield() คือความต่อเนื่อง ซึ่งหมายความว่าหากคุณให้ผลตอบแทนในระหว่างชุดงาน งานอื่นๆ ที่กำหนดเวลาไว้จะทำงานต่อไปในลำดับเดียวกันหลังจากจุดผลตอบแทน วิธีนี้ช่วยป้องกันไม่ให้สคริปต์ของบุคคลที่สามควบคุมลำดับการเรียกใช้โค้ด

การใช้ scheduler.postTask() กับ priority: 'user-blocking' ก็มีแนวโน้มสูงที่จะดําเนินการต่อเนื่องจากลําดับความสําคัญ user-blocking สูง คุณจึงใช้ทางเลือกดังกล่าวแทนจนกว่า scheduler.yield() จะพร้อมใช้งานอย่างแพร่หลายมากขึ้น

การใช้ setTimeout() (หรือ scheduler.postTask() ที่มี priority: 'user-visible' หรือไม่มี priority อย่างชัดแจ้ง) จะกำหนดเวลางานที่อยู่ด้านหลังคิวเพื่อให้งานอื่นๆ ที่รอดำเนินการได้ทำงานก่อนการทำงานอย่างต่อเนื่อง

ผลตอบแทนตามอินพุตที่มี isInputPending()

การสนับสนุนเบราว์เซอร์

  • 87
  • 87
  • x
  • x

isInputPending() API มีวิธีตรวจสอบว่าผู้ใช้ได้พยายามโต้ตอบกับหน้าเว็บหรือไม่ และให้ผลปรากฏเฉพาะเมื่ออินพุตรอดำเนินการอยู่

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

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

  • API อาจแสดงผล false อย่างไม่ถูกต้องในบางกรณีที่ผู้ใช้โต้ตอบ
  • ข้อมูลที่ป้อนไม่ใช่กรณีเดียวที่งานควรให้ผล ภาพเคลื่อนไหวและการอัปเดตอินเทอร์เฟซผู้ใช้ทั่วไปอื่นๆ ก็มีความสำคัญไม่แพ้กันในการแสดงหน้าเว็บที่ปรับเปลี่ยนตามอุปกรณ์
  • ตั้งแต่นั้นมามีการเปิดตัว API ที่ให้ผลตอบแทนได้ครอบคลุมยิ่งขึ้น เช่น scheduler.postTask() และ scheduler.yield() เพื่อจัดการกับข้อกังวลใจ

บทสรุป

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

  • ใช้ได้กับเทรดหลักสำหรับงานสำคัญที่แสดงต่อผู้ใช้
  • ลองทดสอบกับ scheduler.yield()
  • จัดลำดับความสำคัญของงานด้วย postTask()
  • สุดท้าย ให้ทำงานในฟังก์ชันให้น้อยที่สุด

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

ขอขอบคุณเป็นพิเศษที่ Philip Walton ตรวจสอบทางเทคนิคในเอกสารนี้

ภาพขนาดย่อมาจาก Unsplash โดยได้รับความเอื้อเฟื้อจาก Amirali Mirhashemian