Scheduler.yield kaynak denemesi ile tanışın

Kullanıcı girişlerine hızla yanıt veren web siteleri oluşturmak, web performansının en zor yönlerinden biriydi ve Chrome Ekibi web geliştiricilerinin buluşmasına yardımcı olmak için yoğun bir şekilde çalışıyordu. Daha bu yıl, Sonraki Boyamayla Etkileşim (INP) metriğinin deneme durumundan beklemede durumuna geçeceği duyurulmuştu. Mart 2024'te First Input Delay (FID) özelliğinin, Core Web Vital'ın yerini alması bekleniyor.

Web geliştiricilerinin web sitelerini olabildiğince hızlı hale getirmelerine yardımcı olacak yeni API'ler sunmak üzere devam eden çalışmaları kapsamında Chrome Ekibi, Chrome'un 115 sürümünden itibaren scheduler.yield için kaynak denemesi çalıştırmaktadır. scheduler.yield, planlayıcı API'sine yeni bir ekleme olarak önerilmiştir. Bu özellik, geleneksel olarak kullanılan yöntemlere kıyasla ana iş parçacığına kontrol verilmesini daha kolay ve daha iyi bir yol sağlar.

Arttığında

JavaScript, görevleri gerçekleştirmek için tamamlamaya kadar çalıştırma modelini kullanır. Yani bir görev ana iş parçacığında çalıştığında, bu görev tamamlamak için gereken süre boyunca çalışır. Bir görevin tamamlanmasının ardından kontrol, ana iş parçacığına geri getirilir. Bu da ana iş parçacığının sıradaki görevi işlemesine olanak tanır.

Bir görevin hiç bitmediği ekstrem durumlardan (ör. sonsuz döngü) bir yana, sonuç verme, JavaScript'in görev planlama mantığının kaçınılmaz bir yönüdür. Bu olacaktır. Her şey ne zaman meselesi meseledir ve er ya da geç olması daha iyidir. Görevlerin çalıştırılması çok uzun sürdüğünde (tam anlamıyla 50 milisaniyeden uzunsa) uzun görevler olarak kabul edilirler.

Uzun görevler, tarayıcının kullanıcı girişine yanıt verme yetisini geciktirdiği için sayfanın yanıt verme hızının zayıf olmasına yol açar. Uzun görevler ne kadar sık ve ne kadar uzun süre çalışırsa, kullanıcıların sayfanın yavaş olduğu izlenimi edinmesi veya sayfanın tamamen bozulmuş olduğunu düşünme olasılığı o kadar yüksektir.

Bununla birlikte, kodunuzun tarayıcıda bir görevi başlatması, kontrolün ana iş parçacığına geri verilmesi için bu görev bitinceye kadar beklemeniz gerektiği anlamına gelmez. Bir sayfadaki kullanıcı girişine duyarlılığı artırabilirsiniz. Bunun için, görevi açıkça belirterek görevi bir sonraki uygun fırsatta tamamlanacak şekilde bölebilirsiniz. Bu sayede diğer görevler, uzun görevlerin bitmesini beklemek zorunda kalmadıkları duruma göre ana iş parçacığı üzerinde daha erken vakit geçirebilir.

Bir görevi bölmenin girişlere daha iyi yanıt vermeyi nasıl kolaylaştırabileceğini gösteren tasvir. En üstte, uzun bir görev, etkinlik işleyicinin görev tamamlanana kadar çalışmasını engeller. Alt taraftaki parçalanmış görev, etkinlik işleyicinin normalde çalışacağından daha erken çalışmasına olanak tanır.
Kontrolü ana iş parçacığına geri vermeyi gösteren görsel. En üstte, getiri yalnızca bir görev tamamlanıncaya kadar gerçekleşir, yani kontrolü ana iş parçacığına geri verene kadar görevlerin tamamlanması daha uzun sürebilir. En altta, projeden ödün verme açık bir şekilde yapılır ve uzun bir görev daha küçük görevlere bölünür. Bu, kullanıcı etkileşimlerinin daha erken gerçekleştirilmesine olanak tanıyarak giriş duyarlılığını ve INP'yi iyileştirir.

Açıkça izin verdiğinizde tarayıcıya "Yapmak üzere olduğum işin biraz zaman alabileceğinin farkındayım ve kullanıcı girişine veya önemli olabilecek diğer görevlere yanıt vermeden önce bu işin tümünü yapmak zorunda kalmanızı istemiyorum" demiş olursunuz. Geliştiricilerin araç kutusunda bulunan ve kullanıcı deneyimini iyileştirme konusunda çok yararlı olabilecek değerli bir araçtır.

Mevcut getiri stratejileriyle ilgili sorun

Yaygın bir oluşturma yöntemi olarak setTimeout, 0 zaman aşımı değeriyle kullanılır. setTimeout öğesine iletilen geri çağırma, kalan işi sonraki yürütme için sıraya alınacak ayrı bir göreve taşıyacağı için bu işe yarar. Tarayıcının kendi kendine çalışmasını beklemek yerine, "bu büyük işi küçük parçalara bölelim" dersiniz.

Bununla birlikte, setTimeout ile sonuç vermek potansiyel olarak istenmeyen bir yan etkiye yol açar: Getiri noktasından sonra gelen çalışma, görev sırasının sonuna gider. Kullanıcı etkileşimlerine göre planlanan görevler yine de sıranın en üstünde gerektiği gibi yer alır. Ancak açıkça tamamladıktan sonra yapmak istediğiniz geri kalan işler, kendilerinden önce sıraya alınmış rakip kaynaklardan gelen diğer görevler nedeniyle daha da gecikebilir.

Bunun işleyişini görmek için bu Arıza demosunu deneyin veya aşağıdaki yerleştirilmiş sürümde test edin. Demoda, tıklayabileceğiniz birkaç düğme ve bunların altında, görevler çalıştırıldığında günlüğe kaydeden bir kutu bulunur. Sayfaya geldiğinizde aşağıdaki işlemleri gerçekleştirin:

  1. En üstteki Görevleri düzenli aralıklarla çalıştır düğmesini tıklayın. Böylece, engelleme görevleri daha sık çalışacak şekilde planlanır. Bu düğmeyi tıkladığınızda, görev günlüğü setInterval ile engelleme görevi çalıştırıldı yazan birkaç mesajla doldurulur.
  2. Ardından, Döngü çalıştır, her iterasyonda setTimeout ile sonuçlanıyor etiketli düğmeyi tıklayın.

Demonun altındaki kutuda şuna benzer bir metin olduğunu fark edeceksiniz:

Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval

Bu çıkış, setTimeout ile sonuç verirken ortaya çıkan "görev sırası sonu" davranışını gösterir. Çalıştıran döngü beş öğeyi işler ve her biri işlendikten sonra setTimeout ile sonuç verir.

Bu, web'de sık karşılaşılan bir sorunu gösterir: Bir komut dosyasının (özellikle de üçüncü taraf bir komut dosyasının) belirli aralıklarla çalışan bir zamanlayıcı işlevini kaydetmesi anormal değildir. setTimeout ile sonuç vermeyle birlikte gelen "görev sonu sırası" davranışı, diğer görev kaynaklarından yapılan çalışmaların, döngünün sonuç aldıktan sonra yapması gereken kalan işin öncesinde sıraya alınabileceği anlamına gelir.

Uygulamanıza bağlı olarak bu istenen bir sonuç olabilir veya olmayabilir. Ancak birçok durumda, geliştiricilerin ana iş parçacığının kontrolünü bu kadar çabuk bırakma konusunda isteksiz hissetmesinin nedeni de budur. Kullanıcı etkileşimlerinin daha erken gerçekleşme fırsatı olduğu için getiri iyidir ancak kullanıcı olmayan diğer etkileşim çalışmalarının da ana iş parçacığına zaman ayırmasına olanak tanır. Bu gerçek bir sorundur, ancak scheduler.yield sorunu çözmeye yardımcı olabilir.

Şuraya girin: scheduler.yield

scheduler.yield, Chrome'un 115 sürümünden bu yana deneysel web platformu özelliği olarak bayrakla kullanılmaktadır. Sorabileceğiniz sorulardan biri, "setTimeout zaten bunu yaparken neden özel bir işleve ihtiyacım var?" olabilir.

Getirinin setTimeout için bir tasarım hedefi olmadığını, daha ziyade gelecekteki bir noktada (0 zaman aşımı değeri belirtilmiş olsa bile) geri çağırmanın planlanması açısından iyi bir yan etki olduğunu belirtmek gerekir. Ancak unutulmaması gereken en önemli nokta, setTimeout kullanıldığında elde edilen çalışmanın kalan işi görev sırasının arkasına göndermesidir. Varsayılan olarak, scheduler.yield geri kalan çalışmaları sıranın önüne gönderir. Yani, çalışmayı bitirdikten hemen sonra devam etmek istediğiniz çalışmalar, diğer kaynaklardaki görevlere geri dönmez (önemli kullanıcı etkileşimleri hariç).

scheduler.yield, ana iş parçacığına neden olan ve çağrıldığında bir Promise döndüren bir işlevdir. Yani bir async işlevinde bunu await yapabilirsiniz:

async function yieldy () {
  // Do some work...
  // ...

  // Yield!
  await scheduler.yield();

  // Do some more work...
  // ...
}

scheduler.yield uygulamasının nasıl çalıştığını görmek için şunları yapın:

  1. chrome://flags adresine gidiş rotasını izle.
  2. Deneysel Web Platformu özellikleri denemesini etkinleştirin. Bunu yaptıktan sonra Chrome'u yeniden başlatmanız gerekebilir.
  3. Demo sayfasına gidin veya bu listenin altındaki yerleşik sürümünü kullanın.
  4. En üstteki Görevleri düzenli aralıklarla çalıştır etiketli düğmeyi tıklayın.
  5. Son olarak, Döngü çalıştır, her iterasyonda scheduler.yield sonucunu veren düğmeyi tıklayın.

Sayfanın alt kısmındaki kutuda yer alan çıkış aşağıdaki gibi görünür:

Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval

setTimeout kullanarak sonuç veren demodan farklı olarak, döngü (her yinelemeden sonra verilese de) geri kalan işi sıranın arkasına göndermediğini, onun önüne göndermediğini görebilirsiniz. Bu size iki ağın en iyi özelliklerini sunar: Web sitenizin giriş duyarlılığını iyileştirebilir ve sonuç sonra yapmak istediğiniz çalışmanın gecikmemesini sağlayabilirsiniz.

Mutlaka deneyin!

scheduler.yield size ilginç geliyorsa ve denemek istiyorsanız Chrome'un 115 sürümünden başlayarak bunu iki şekilde yapabilirsiniz:

  1. Yerel olarak scheduler.yield ile deneme yapmak istiyorsanız Chrome'un adres çubuğuna chrome://flags yazıp girin ve Deneysel Web Platformu Özellikleri bölümündeki açılır menüden Etkinleştir'i seçin. Bu işlem scheduler.yield (ve diğer tüm deneysel özellikler) yalnızca Chrome örneğinizde kullanılabilir hale gelir.
  2. Herkese açık bir kaynaktaki gerçek Chromium kullanıcıları için scheduler.yield özelliğini etkinleştirmek istiyorsanız scheduler.yield kaynak denemesine kaydolmanız gerekir. Böylece, önerilen özelliklerle belirli bir süre boyunca güvenli bir şekilde denemeler yapabilirsiniz ve Chrome Ekibi'ne, bu özelliklerin sahada nasıl kullanıldığına ilişkin değerli bilgiler edinebilirsiniz. Kaynak denemelerinin işleyiş şekli hakkında daha fazla bilgi için bu kılavuzu okuyun.

Bu özelliği uygulamayan tarayıcıları desteklemeye devam ederken scheduler.yield nasıl kullanacağınız, hedeflerinize bağlıdır. Resmi çoklu dolguyu kullanabilirsiniz. Çoklu dolgu, sizin durumunuz için aşağıdakiler geçerliyse kullanışlıdır:

  1. Uygulamanızda görev planlamak için zaten scheduler.postTask kullanıyorsunuz.
  2. Görev ve getiri öncelikleri belirleyebilmeniz gerekir.
  3. scheduler.postTask API'sinin sunduğu TaskController sınıfı aracılığıyla görevleri iptal edebilmek veya yeniden önceliklendirebilmek istiyorsunuz.

Bu, durumunuzu açıklamıyorsa çoklu dolgu size uygun olmayabilir. Bu durumda, kendi yedeğinizi birkaç şekilde geri alabilirsiniz. İlk yaklaşım, varsa scheduler.yield öğesini kullanır, ancak kullanılamadığı takdirde setTimeout değerine geri döner:

// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to setTimeout:
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

Bu işe yarayabilir, ancak tahmin edeceğiniz gibi, scheduler.yield özelliğini desteklemeyen tarayıcılar "sıranın önü" davranışı olmadan veri verir. Bu durumda hiç sonuç elde etmemeyi tercih ediyorsanız kullanılabiliyorsa scheduler.yield yöntemini kullanan, ancak yoksa hiç getiri sağlamayacak başka bir yaklaşımı deneyebilirsiniz:

// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to nothing:
  return;
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

scheduler.yield, planlayıcı API'sine heyecan verici bir katkı sağlıyor. Bu API'nin, geliştiricilerin yanıt vermeyi mevcut getiri stratejilerinden daha kolay bir şekilde iyileştirmesini sağlamayı umuyoruz. scheduler.yield API'nin işinize yarayacağını düşünüyorsanız lütfen bu API'yi iyileştirmeye yardımcı olmak için araştırmamıza katılın ve nasıl daha da geliştirilebileceğine dair geri bildirimde bulunun.

Unsplash'tan Jonathan Allison'ın hazırladığı lokomotif resim.