Ngày xuất bản: 6 tháng 3 năm 2025
Một trang có cảm giác chậm chạp và không phản hồi khi các tác vụ có thời gian thực hiện dài khiến luồng chính bận, ngăn luồng này thực hiện các công việc quan trọng khác, chẳng hạn như phản hồi hoạt động đầu vào của người dùng. Do đó, ngay cả các chế độ kiểm soát biểu mẫu tích hợp sẵn cũng có thể xuất hiện bị hỏng đối với người dùng (như thể trang bị đóng băng), chưa kể đến các thành phần tuỳ chỉnh phức tạp hơn.
scheduler.yield()
là một cách nhường quyền cho luồng chính – cho phép trình duyệt chạy mọi công việc đang chờ xử lý có mức độ ưu tiên cao – sau đó tiếp tục thực thi ở nơi đã dừng. Điều này giúp trang phản hồi nhanh hơn và do đó, giúp cải thiện Tương tác với thời gian hiển thị tiếp theo (INP).
scheduler.yield
cung cấp một API tiện dụng thực hiện đúng như những gì nó nói: quá trình thực thi hàm mà nó được gọi sẽ tạm dừng tại biểu thức await scheduler.yield()
và nhường quyền cho luồng chính, chia nhỏ tác vụ. Việc thực thi phần còn lại của hàm (gọi là phần tiếp tục của hàm) sẽ được lên lịch chạy trong một tác vụ vòng lặp sự kiện mới.
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
Lợi ích cụ thể của scheduler.yield
là phần tiếp tục sau lệnh yield được lên lịch chạy trước khi chạy bất kỳ tác vụ tương tự nào khác mà trang đã xếp vào hàng đợi. Thao tác này ưu tiên việc tiếp tục một tác vụ hơn là bắt đầu các tác vụ mới.
Bạn cũng có thể dùng các hàm như setTimeout
hoặc scheduler.postTask
để chia nhỏ các tác vụ, nhưng những hàm tiếp tục đó thường chạy sau mọi tác vụ mới đã được xếp hàng đợi, có thể gây ra tình trạng chậm trễ kéo dài giữa việc nhường quyền cho luồng chính và hoàn tất công việc.
Các hoạt động tiếp tục được ưu tiên sau khi tạo
scheduler.yield
là một phần của Prioritized Task Scheduling API. Là nhà phát triển web, chúng ta thường không nói về thứ tự mà vòng lặp sự kiện chạy các tác vụ theo mức độ ưu tiên rõ ràng, nhưng mức độ ưu tiên tương đối luôn có sẵn, chẳng hạn như lệnh gọi lại requestIdleCallback
chạy sau mọi lệnh gọi lại setTimeout
được xếp hàng đợi hoặc trình nghe sự kiện đầu vào được kích hoạt thường chạy trước một tác vụ được xếp hàng đợi bằng setTimeout(callback, 0)
.
Tính năng Lập lịch tác vụ theo mức độ ưu tiên chỉ giúp bạn xác định rõ ràng hơn, giúp bạn dễ dàng xác định tác vụ nào sẽ chạy trước tác vụ khác và cho phép điều chỉnh mức độ ưu tiên để thay đổi thứ tự thực hiện nếu cần.
Như đã đề cập, việc tiếp tục thực thi một hàm sau khi tạo bằng scheduler.yield()
sẽ có mức độ ưu tiên cao hơn so với việc bắt đầu các tác vụ khác. Khái niệm hướng dẫn là phần tiếp tục của một tác vụ phải chạy trước, sau đó mới chuyển sang các tác vụ khác. Nếu tác vụ là mã hoạt động bình thường, định kỳ tạo ra để trình duyệt có thể thực hiện những việc quan trọng khác (chẳng hạn như phản hồi hoạt động đầu vào của người dùng), thì tác vụ đó sẽ không bị phạt vì tạo ra bằng cách được ưu tiên sau các tác vụ tương tự khác.
Sau đây là một ví dụ: hai hàm được xếp hàng đợi để chạy trong các tác vụ khác nhau bằng cách sử dụng setTimeout
.
setTimeout(myJob);
setTimeout(someoneElsesJob);
Trong trường hợp này, hai lệnh gọi setTimeout
nằm ngay cạnh nhau, nhưng trong một trang thực tế, chúng có thể được gọi ở những vị trí hoàn toàn khác nhau, chẳng hạn như một tập lệnh bên thứ nhất và một tập lệnh bên thứ ba thiết lập độc lập công việc để chạy, hoặc có thể là hai tác vụ từ các thành phần riêng biệt được kích hoạt sâu trong trình lập lịch của khung.
Sau đây là ví dụ về cách hoạt động này có thể xuất hiện trong Công cụ cho nhà phát triển:
myJob
được gắn cờ là một tác vụ dài, ngăn trình duyệt thực hiện bất kỳ thao tác nào khác trong khi tác vụ này đang chạy. Giả sử đó là từ một tập lệnh của bên thứ nhất, chúng ta có thể chia nhỏ như sau:
function myJob() {
// Run part 1.
myJobPart1();
// Yield with setTimeout() to break up long task, then run part2.
setTimeout(myJobPart2, 0);
}
Vì myJobPart2
được lên lịch chạy cùng với setTimeout
trong myJob
, nhưng lịch biểu đó chạy sau khi someoneElsesJob
đã được lên lịch, nên đây là cách thực thi:
Chúng tôi đã chia nhỏ tác vụ bằng setTimeout
để trình duyệt có thể phản hồi trong quá trình myJob
, nhưng giờ đây, phần thứ hai của myJob
chỉ chạy sau khi someoneElsesJob
hoàn tất.
Trong một số trường hợp, điều đó có thể chấp nhận được, nhưng thường thì đây không phải là cách tối ưu. myJob
đang nhường quyền cho luồng chính để đảm bảo trang có thể phản hồi hoạt động đầu vào của người dùng, không hoàn toàn từ bỏ luồng chính. Trong trường hợp someoneElsesJob
đặc biệt chậm hoặc nhiều công việc khác ngoài someoneElsesJob
cũng đã được lên lịch, thì có thể mất một thời gian dài trước khi nửa sau của myJob
được chạy. Có thể đó không phải là ý định của nhà phát triển khi họ thêm setTimeout
đó vào myJob
.
Nhập scheduler.yield()
, thao tác này sẽ đặt phần tiếp tục của mọi hàm gọi hàm này vào hàng đợi có mức độ ưu tiên cao hơn một chút so với việc bắt đầu bất kỳ tác vụ tương tự nào khác. Nếu myJob
được thay đổi để sử dụng:
async function myJob() {
// Run part 1.
myJobPart1();
// Yield with scheduler.yield() to break up long task, then run part2.
await scheduler.yield();
myJobPart2();
}
Giờ đây, quá trình thực thi sẽ có dạng như sau:
Trình duyệt vẫn có cơ hội phản hồi, nhưng giờ đây, việc tiếp tục tác vụ myJob
được ưu tiên hơn việc bắt đầu tác vụ someoneElsesJob
mới, vì vậy, myJob
sẽ hoàn tất trước khi someoneElsesJob
bắt đầu. Điều này gần với kỳ vọng về việc nhường quyền kiểm soát cho luồng chính để duy trì khả năng phản hồi hơn nhiều, chứ không phải từ bỏ hoàn toàn luồng chính.
Kế thừa mức độ ưu tiên
Là một phần của Prioritized Task Scheduling API (API Lập lịch tác vụ theo mức độ ưu tiên) lớn hơn, scheduler.yield()
kết hợp tốt với các mức độ ưu tiên rõ ràng có trong scheduler.postTask()
. Nếu bạn không đặt mức độ ưu tiên một cách rõ ràng, thì scheduler.yield()
trong lệnh gọi lại scheduler.postTask()
về cơ bản sẽ hoạt động giống như ví dụ trước.
Tuy nhiên, nếu bạn đặt mức độ ưu tiên, chẳng hạn như sử dụng mức độ ưu tiên thấp 'background'
:
async function lowPriorityJob() {
part1();
await scheduler.yield();
part2();
}
scheduler.postTask(lowPriorityJob, {priority: 'background'});
Hoạt động tiếp tục sẽ được lên lịch với mức độ ưu tiên cao hơn các tác vụ 'background'
khác (nhận được hoạt động tiếp tục được ưu tiên như dự kiến trước khi có bất kỳ công việc 'background'
nào đang chờ xử lý) nhưng vẫn có mức độ ưu tiên thấp hơn các tác vụ mặc định hoặc có mức độ ưu tiên cao khác; đây vẫn là công việc 'background'
.
Điều này có nghĩa là nếu bạn lên lịch cho công việc có mức độ ưu tiên thấp bằng 'background'
scheduler.postTask()
(hoặc bằng requestIdleCallback
), thì phần tiếp tục sau scheduler.yield()
trong cũng sẽ đợi cho đến khi hầu hết các tác vụ khác hoàn tất và luồng chính ở trạng thái rảnh để chạy. Đây chính xác là những gì bạn muốn khi nhường quyền trong một công việc có mức độ ưu tiên thấp.
Cách sử dụng API
Hiện tại, scheduler.yield()
chỉ có trong các trình duyệt dựa trên Chromium. Vì vậy, để sử dụng lệnh này, bạn cần phát hiện tính năng và quay lại cách tạo thứ cấp cho các trình duyệt khác.
scheduler-polyfill
là một polyfill nhỏ cho scheduler.postTask
và scheduler.yield
. Polyfill này sử dụng kết hợp các phương thức để mô phỏng nhiều chức năng của các API lập lịch trong những trình duyệt khác (mặc dù không hỗ trợ tính năng kế thừa mức độ ưu tiên scheduler.yield()
).
Đối với những người muốn tránh polyfill, một phương pháp là tạo bằng setTimeout()
và chấp nhận mất khả năng tiếp tục được ưu tiên, hoặc thậm chí không tạo trong các trình duyệt không được hỗ trợ nếu điều đó không chấp nhận được. Hãy xem tài liệu scheduler.yield()
trong phần Tối ưu hoá các tác vụ dài để biết thêm thông tin.
Bạn cũng có thể dùng wicg-task-scheduling
các loại để kiểm tra loại và hỗ trợ IDE nếu bạn đang phát hiện tính năng scheduler.yield()
và tự thêm giải pháp dự phòng.
Tìm hiểu thêm
Để biết thêm thông tin về API này và cách API này tương tác với mức độ ưu tiên của tác vụ và scheduler.postTask()
, hãy xem tài liệu scheduler.yield()
và Lập lịch tác vụ theo mức độ ưu tiên trên MDN.
Để tìm hiểu thêm về các tác vụ dài, cách các tác vụ này ảnh hưởng đến trải nghiệm người dùng và những việc cần làm đối với các tác vụ này, hãy đọc về cách tối ưu hoá các tác vụ dài.