Uzun görevleri bölmek için scheduler.yield() işlevini kullanma

Brendan Kenny
Brendan Kenny

Yayınlanma tarihi: 6 Mart 2025

Browser Support

  • Chrome: 129.
  • Edge: 129.
  • Firefox: 142.
  • Safari: not supported.

Source

Uzun görevler ana iş parçacığını meşgul ederek kullanıcı girişine yanıt verme gibi diğer önemli işleri yapmasını engellediğinde sayfa yavaş ve yanıt vermiyormuş gibi görünür. Sonuç olarak, daha karmaşık özel bileşenleri bir kenara bırakın, yerleşik form kontrolleri bile kullanıcılara bozuk görünebilir (sayfa donmuş gibi).

scheduler.yield(), ana iş parçacığına geçiş yapmanın bir yoludur. Tarayıcının bekleyen yüksek öncelikli işleri çalıştırmasına izin verir ve ardından yürütmeye kaldığı yerden devam eder. Bu, sayfanın daha duyarlı olmasını sağlar ve dolayısıyla Sonraki Boyamayla Etkileşim (INP) değerinin iyileştirilmesine yardımcı olur.

scheduler.yield, tam olarak söylediği şeyi yapan ergonomik bir API sunar: İçinde çağrıldığı işlevin yürütülmesi await scheduler.yield() ifadesinde duraklatılır ve ana iş parçacığına bırakılarak görev bölünür. İşlevin geri kalanının yürütülmesi (işlevin devamı olarak adlandırılır) yeni bir etkinlik döngüsü görevinde çalışacak şekilde planlanır.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

scheduler.yield'nın asıl avantajı, yield'dan sonraki devamın, sayfa tarafından sıraya alınmış diğer benzer görevler çalıştırılmadan önce çalıştırılacak şekilde planlanmasıdır. Yeni görevlere başlamak yerine mevcut görevlerin devamına öncelik verir.

setTimeout veya scheduler.postTask gibi işlevler de görevleri bölmek için kullanılabilir ancak bu devam ettirmeler genellikle halihazırda sıraya alınmış yeni görevlerden sonra çalışır. Bu da ana iş parçacığına geçiş ile işlerinin tamamlanması arasında uzun gecikmeler yaşanma riskini artırır.

Kontrolü bıraktıktan sonra öncelikli devam ettirme

scheduler.yield, Prioritized Task Scheduling API'nin bir parçasıdır. Web geliştiriciler olarak, etkinlik döngüsünün görevleri hangi sırayla çalıştırdığı hakkında genellikle açık öncelikler açısından konuşmayız. Ancak göreceli öncelikler her zaman vardır. Örneğin, requestIdleCallback kuyruğa alınmış geri çağırma işlemlerinden sonra çalışan bir geri çağırma işlemi setTimeout veya tetiklenen bir giriş etkinliği dinleyicisi genellikle setTimeout(callback, 0) ile kuyruğa alınmış bir görevden önce çalışır.

Öncelikli görev planlama, hangi görevin diğerinden önce çalışacağını anlamayı kolaylaştırarak bu durumu daha açık hale getirir ve gerekirse öncelikleri ayarlayarak yürütme sırasını değiştirmenize olanak tanır.

Belirtildiği gibi, scheduler.yield() ile kontrolü bıraktıktan sonra bir işlevin yürütülmeye devam etmesi, diğer görevleri başlatmaktan daha yüksek önceliğe sahiptir. Buradaki temel kavram, diğer görevlere geçmeden önce bir görevin devamlılığının sağlanması gerektiğidir. Görev, tarayıcının diğer önemli işlemleri (ör. kullanıcı girişine yanıt verme) yapabilmesi için düzenli olarak kontrolü bırakan iyi davranışlı bir kodsa diğer benzer görevlerden sonra önceliklendirilerek kontrolü bıraktığı için cezalandırılmamalıdır.

İki işlevin setTimeout kullanılarak farklı görevlerde çalıştırılmak üzere sıraya alındığı bir örneği aşağıda bulabilirsiniz.

setTimeout(myJob);
setTimeout(someoneElsesJob);

Bu örnekte, iki setTimeout çağrısı yan yana olsa da gerçek bir sayfada tamamen farklı yerlerde çağrılabilir. Örneğin, birinci taraf komut dosyası ve üçüncü taraf komut dosyası, çalışacak işi bağımsız olarak ayarlayabilir veya ayrı bileşenlerden gelen iki görev, çerçevenizin zamanlayıcısında derinlemesine tetiklenebilir.

Bu çalışmanın DevTools'da nasıl görünebileceği aşağıda gösterilmiştir:

Chrome Geliştirici Araçları performans panelinde iki görev gösteriliyor. Her ikisi de uzun görevler olarak belirtiliyor. "myJob" işlevi ilk görevin tamamını, "someoneElsesJob" işlevi ise ikinci görevin tamamını kapsıyor.

myJob uzun bir görev olarak işaretlenir ve çalışırken tarayıcının başka bir işlem yapmasını engeller. Birinci taraf komut dosyasından geldiğini varsayarsak bunu şu şekilde ayırabiliriz:

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

myJobPart2, myJob içinde setTimeout ile birlikte çalışacak şekilde planlandığı ancak bu planlama someoneElsesJob zaten planlandıktan sonra yapıldığı için yürütme şu şekilde görünür:

Chrome Geliştirici Araçları performans panelinde üç görev gösteriliyor. Birincisi "myJobPart1" işlevini çalıştırır, ikincisi "someoneElsesJob" işlevini çalıştıran uzun bir görevdir ve son olarak üçüncü görev "myJobPart2" işlevini çalıştırır.

setTimeout ile görevi böldük. Böylece tarayıcı, myJob'nin ortasında yanıt verebiliyordu. Ancak artık myJob'nin ikinci kısmı yalnızca someoneElsesJob tamamlandıktan sonra çalışıyor.

Bu durum bazı durumlarda sorun olmayabilir ancak genellikle ideal değildir. myJob, ana iş parçacığını tamamen bırakmak için değil, sayfanın kullanıcı girişine yanıt vermeye devam edebilmesi için ana iş parçacığına geçiyordu. someoneElsesJob özellikle yavaş olduğunda veya someoneElsesJob dışında başka birçok iş de planlandığında myJob'nin ikinci yarısının çalıştırılması uzun zaman alabilir. Geliştirici, setTimeout öğesini myJob öğesine eklerken muhtemelen bu sonucu amaçlamamıştır.

scheduler.yield() girin. Bu, onu çağıran işlevlerin devamını diğer benzer görevleri başlatmaktan biraz daha yüksek öncelikli bir sıraya yerleştirir. myJob, bu özelliği kullanacak şekilde değiştirilirse:

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

Şimdi yürütme şu şekilde görünür:

Chrome Geliştirici Araçları performans panelinde iki görev gösteriliyor. Her ikisi de uzun görevler olarak belirtiliyor. "myJob" işlevi ilk görevin tamamını, "someoneElsesJob" işlevi ise ikinci görevin tamamını kapsıyor.

Tarayıcı yine yanıt verebilir ancak artık myJob görevinin devamı, yeni görev someoneElsesJob'nin başlatılmasından öncelikli olduğundan myJob, someoneElsesJob başlamadan önce tamamlanır. Bu, ana iş parçacığını tamamen bırakmak yerine yanıt verebilirliği korumak için ana iş parçacığına geçme beklentisine çok daha yakındır.

Öncelik devralma

Daha büyük olan Öncelikli Görev Zamanlama API'sinin bir parçası olarak scheduler.yield(), scheduler.postTask()'de bulunan açık önceliklerle iyi çalışır. Öncelik açıkça ayarlanmadığında, scheduler.postTask() geri çağırması içindeki bir scheduler.yield() temelde önceki örnekle aynı şekilde davranır.

Ancak düşük 'background' önceliği kullanmak gibi bir öncelik ayarlanırsa:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

Devam etme işlemi, diğer 'background' görevlerinden daha yüksek öncelik verilerek planlanır. Böylece, bekleyen 'background' işlerinden önce beklenen öncelikli devam etme işlemi gerçekleştirilir. Ancak bu işlem, diğer varsayılan veya yüksek öncelikli görevlerden daha düşük önceliğe sahiptir ve 'background' işi olarak kalır.

Bu nedenle, 'background' scheduler.postTask() ile (veya requestIdleCallback ile) düşük öncelikli bir iş planlarsanız scheduler.yield() içinde devam etme işlemi de diğer görevlerin çoğu tamamlanana ve ana ileti dizisi boşta kalana kadar bekler. Bu, düşük öncelikli bir işte bekleme işleminden istediğiniz şeydir.

API nasıl kullanılır?

scheduler.yield() şu anda yalnızca Chromium tabanlı tarayıcılarda kullanılabilir. Bu nedenle, özelliği algılamanız ve diğer tarayıcılarda ikincil bir yöntemle geri dönmeniz gerekir.

scheduler-polyfill, scheduler.postTask ve scheduler.yield için küçük bir polyfill'dir. Diğer tarayıcılardaki planlama API'lerinin gücünün çoğunu taklit etmek için dahili olarak bir yöntem kombinasyonu kullanır (ancak scheduler.yield() öncelik devralma desteklenmez).

Polyfill kullanmak istemeyenler için bir yöntem, setTimeout() kullanarak sonuç üretmek ve öncelikli devamlılık kaybını kabul etmektir. Bu kabul edilemezse desteklenmeyen tarayıcılarda sonuç üretilmez. Daha fazla bilgi için scheduler.yield() Uzun görevleri optimize etme bölümündeki dokümanları inceleyin.

wicg-task-scheduling türleri, scheduler.yield() özelliğini algılıyorsanız ve geri dönüşü kendiniz ekliyorsanız tür kontrolü ve IDE desteği almak için de kullanılabilir.

Daha fazla bilgi

API ve görev öncelikleriyle scheduler.postTask() ile etkileşimi hakkında daha fazla bilgi için MDN'deki scheduler.yield() ve Öncelikli Görev Planlama belgelerine göz atın.

Uzun görevler, bunların kullanıcı deneyimini nasıl etkilediği ve bu görevlerle ilgili olarak yapılması gerekenler hakkında daha fazla bilgi edinmek için uzun görevleri optimize etme başlıklı makaleyi inceleyin.