Lập lịch JS hiệu quả hơn với isInputPending()

API JavaScript mới có thể giúp bạn tránh sự đánh đổi giữa hiệu suất tải và khả năng phản hồi của dữ liệu đầu vào.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Tải nhanh rất khó. Các trang web tận dụng JS để hiển thị nội dung của trang web hiện tại phải cân bằng giữa hiệu suất tải và đầu vào khả năng phản hồi: thực hiện mọi công việc cần thiết đối với màn hình cùng một lúc (hiệu suất tải tốt hơn, khả năng phản hồi dữ liệu đầu vào kém hơn) hoặc chia công việc thành các công việc nhỏ hơn để có thể kịp thời phản ứng đầu vào và tô màu (hiệu suất tải kém hơn, dữ liệu đầu vào tốt hơn thời gian phản hồi).

Để loại bỏ sự cần thiết phải đánh đổi này, Facebook đã đề xuất và triển khai API isInputPending() trong Chromium để cải thiện khả năng phản hồi mà không cần mang lại kết quả. Dựa trên ý kiến phản hồi về bản dùng thử theo nguyên gốc, chúng tôi đã cập nhật một số nội dung đối với API và vui mừng thông báo rằng API hiện đang được vận chuyển theo mặc định trong Chromium 87!

Khả năng tương thích với trình duyệt

Hỗ trợ trình duyệt

  • Chrome: 87.
  • Cạnh: 87.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

isInputPending() được xuất bản trong các trình duyệt dựa trên Chromium kể từ phiên bản 87. Chưa có trình duyệt nào khác báo hiệu ý định gửi API.

Thông tin khái quát

Hầu hết mọi công việc trong hệ sinh thái JS hiện nay đều được thực hiện trên một luồng duy nhất: luồng chính. Điều này mang lại mô hình thực thi mạnh mẽ cho nhà phát triển, nhưng trải nghiệm người dùng (cụ thể là tính phản hồi) có thể bị ảnh hưởng đáng kể nếu tập lệnh thực thi trong một khoảng thời gian dài bất cứ lúc nào. Nếu trang hoạt động hiệu quả trong khi kích hoạt sự kiện đầu vào, ví dụ: trang sẽ chỉ xử lý sự kiện nhập lượt nhấp sau khi hoạt động đó hoàn tất hoàn tất.

Phương pháp hay nhất hiện tại là giải quyết vấn đề này bằng cách phá JavaScript thành các khối nhỏ hơn. Khi trang đang tải, trang có thể chạy lệnh một bit JavaScript, sau đó tạo ra và chuyển quyền kiểm soát trở lại trình duyệt. Chiến lược phát hành đĩa đơn sau đó trình duyệt có thể kiểm tra hàng đợi sự kiện đầu vào của nó và xem liệu có bất cứ điều gì cần thông báo cho trang. Sau đó, trình duyệt có thể quay lại chạy JavaScript sẽ chặn khi các thành phần này được thêm vào. Điều này giúp ích cho bạn, nhưng có thể gây ra các vấn đề khác.

Mỗi khi trang cấp lại quyền kiểm soát cho trình duyệt, phải mất một chút thời gian để trình duyệt để kiểm tra hàng đợi sự kiện đầu vào, xử lý sự kiện và chọn sự kiện tiếp theo Khối JavaScript. Mặc dù trình duyệt phản hồi các sự kiện nhanh hơn, nhưng nhìn chung thì thì thời gian tải của trang bị chậm. Và nếu chúng ta tạo ra quá thường xuyên, trang tải quá chậm. Nếu chúng tôi mang lại ít thường xuyên hơn, trình duyệt sẽ mất nhiều thời gian hơn để phản hồi sự kiện của người dùng và mọi người sẽ cảm thấy thất vọng. Không thú vị.

Sơ đồ cho thấy khi bạn chạy các tác vụ JS dài, trình duyệt sẽ có ít thời gian hơn để gửi các sự kiện.

Tại Facebook, chúng tôi muốn biết mọi thứ sẽ như thế nào nếu nảy ra một phương pháp tải mới sẽ giúp loại bỏ sự đánh đổi khó chịu này. T4 đã liên hệ với bạn bè tại Chrome về vấn đề này và đưa ra đề xuất với giá isInputPending(). isInputPending() API là API đầu tiên sử dụng khái niệm làm gián đoạn hoạt động đầu vào của người dùng trên web và cho phép JavaScript có thể kiểm tra đầu vào mà không cần phải phục vụ trình duyệt.

Sơ đồ cho thấy isInputPending() cho phép JS kiểm tra xem có hoạt động đầu vào nào của người dùng đang chờ xử lý hay không mà không hoàn toàn tạo ra lệnh thực thi cho trình duyệt.

Do quan tâm đến API, chúng tôi đã hợp tác với các đồng nghiệp tại Chrome để triển khai và gửi tính năng này trong Chromium. Với sự trợ giúp của Chrome kỹ sư, chúng tôi đã nhận được các bản vá hỗ trợ bản dùng thử theo nguyên gốc (đây là một cách để Chrome thử nghiệm các thay đổi và nhận ý kiến phản hồi của nhà phát triển trước khi phát hành đầy đủ một API).

Chúng tôi hiện đã tiếp nhận ý kiến phản hồi từ bản dùng thử theo nguyên gốc và từ các thành viên khác của Nhóm làm việc về hiệu suất web W3C và triển khai các thay đổi đối với API.

Ví dụ: trình lập lịch biểu mang lại lợi nhuận

Giả sử bạn có một loạt tác vụ chặn hiển thị cần làm để tải trang, ví dụ: tạo mã đánh dấu từ các thành phần, loại bỏ số nguyên tố, hoặc chỉ là vẽ một vòng quay tải thú vị. Mỗi phần này được chia thành mục công việc. Sử dụng mẫu trình lập lịch biểu, hãy phác thảo cách chúng ta có thể xử lý công việc của chúng ta trong hàm processWorkQueue() giả định:

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Bằng cách gọi processWorkQueue() sau này trong một tác vụ vĩ mô mới qua setTimeout(), chúng ta mang lại cho trình duyệt khả năng duy trì phản hồi đôi chút với thông tin đầu vào (nó có thể chạy trình xử lý sự kiện trước khi công việc tiếp tục) trong khi vẫn quản lý để chạy tương đối mà không bị gián đoạn. Tuy nhiên, công việc khác có thể khiến chúng tôi bị hoãn trong thời gian dài muốn kiểm soát vòng lặp sự kiện hoặc kéo dài thêm đến QUANTUM mili giây độ trễ của sự kiện.

Điều này là bình thường, nhưng chúng ta có thể làm tốt hơn không? Chắc chắn!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Bằng cách thực hiện một cuộc gọi đến navigator.scheduling.isInputPending(), chúng tôi có thể phản hồi dữ liệu đầu vào nhanh hơn mà vẫn đảm bảo rằng công việc chặn hiển thị nếu không thì thực thi không bị gián đoạn. Nếu chúng tôi không quan tâm đến việc xử lý bất cứ điều gì ngoài dữ liệu đầu vào (ví dụ: tô màu) cho đến khi công việc hoàn tất, chúng ta có thể tăng một cách thủ công độ dài của QUANTUM.

Theo mặc định, "liên tục" các sự kiện không được trả về từ isInputPending(). Các bao gồm mousemove, pointermove và các nguồn khác. Nếu bạn muốn lợi nhuận những thông tin này thì không sao cả. Bằng cách cung cấp một đối tượng cho isInputPending() với Đã đặt includeContinuous thành true, chúng ta đã sẵn sàng:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Vậy là xong! Các khung như React đang xây dựng khả năng hỗ trợ của isInputPending() cho thư viện lập lịch lõi bằng cách sử dụng logic tương tự. Hy vọng rằng việc này sẽ giúp những nhà phát triển sử dụng các khung này để có thể hưởng lợi từ isInputPending() hậu trường mà không cần phải viết lại nhiều.

Lợi nhuận không phải lúc nào cũng xấu

Lưu ý rằng giảm sản lượng không phải là giải pháp phù hợp cho mọi trường hợp sử dụng trường hợp. Có nhiều lý do để trả lại quyền kiểm soát cho trình duyệt ngoài việc xử lý sự kiện đầu vào, chẳng hạn như thực hiện kết xuất và thực thi các tập lệnh khác trên trang.

Có những trường hợp mà trình duyệt không thể phân bổ chính xác đang chờ xử lý sự kiện đầu vào. Cụ thể, việc thiết lập các đoạn video và mặt nạ phức tạp cho nhiều nguồn gốc iframe có thể báo cáo âm tính giả (tức là isInputPending() có thể trả về đột ngột false khi nhắm mục tiêu các khung này). Hãy đảm bảo rằng bạn sinh lời đủ thường xuyên nếu trang web của bạn không yêu cầu tương tác với khung phụ được cách điệu.

Hãy lưu ý đến các trang khác có cùng vòng lặp sự kiện. Trên các nền tảng như như Chrome dành cho Android, việc nhiều nguồn gốc chia sẻ một sự kiện là khá phổ biến vòng lặp. isInputPending() sẽ không bao giờ trả về true nếu dữ liệu đầu vào được gửi đến một khung trên nhiều nguồn gốc, và do đó các trang chạy ở chế độ nền có thể ảnh hưởng đến khả năng thích ứng của trang trên nền trước. Bạn nên giảm, trì hoãn hoặc lợi nhuận thường xuyên hơn khi thực hiện thao tác trong nền bằng API Mức độ hiển thị của trang.

Bạn nên cân nhắc thận trọng khi sử dụng isInputPending(). Nếu không có phải thực hiện công việc chặn người dùng, sau đó đối xử tốt với những người khác trong vòng lặp sự kiện bằng cách mang lại thường xuyên hơn. Những tác vụ dài có thể gây hại.

Phản hồi

  • Để lại ý kiến phản hồi về thông số kỹ thuật trong kho lưu trữ is-input-pending.
  • Liên hệ với @acomminos (một trong những tác giả của thông số kỹ thuật) trên Twitter.

Kết luận

Chúng tôi rất vui vì isInputPending() sắp ra mắt và nhà phát triển có thể bắt đầu sử dụng ngay hôm nay. Đây là API lần đầu tiên Facebook xây dựng một web API mới và biến nó từ bước ươm mầm ý tưởng đến đề xuất tiêu chuẩn để thực sự di chuyển trong trình duyệt. Chúng tôi muốn cảm ơn tất cả những người đã giúp chúng tôi đạt được điều này và gửi lời cảm ơn đặc biệt đến tất cả mọi người tại Chrome đã giúp chúng tôi phát triển ý tưởng này và gửi hàng đi!

Ảnh chính của Will H McMahan trên Không hiển thị màn hình.