Scheduler.yield kaynak denemesi ile tanışın

Kullanıcı girişlerine hızlı yanıt veren web siteleri oluşturmak, web performansının en zorlu yönlerinden biri olmuştur. Bu, Chrome Ekibi'nin web geliştiricilerinin uyum sağlamasına yardımcı olmak için sıkı bir şekilde çalıştığı bir çalışmadır. Yalnızca bu yıl, Sonraki Boyamayla Etkileşim (INP) metriğinin deneyselden beklemede durumuna geçeceği duyurulmuştu. Mart 2024'te First Giriş Gecikmesi (FID) yerine Core Web Vitals olarak kullanılmaya hazır.

Web geliştiricilerin web sitelerini olabildiğince hızlı hale getirmelerine yardımcı olacak yeni API'ler sunma çabamızın bir parçası olarak Chrome Ekibi, Chrome'un 115 sürümünden itibaren scheduler.yield için kaynak denemesini çalıştırmaya devam ediyor. scheduler.yield, zamanlayıcı API'sine eklenen ve geleneksel olarak kullanılan yöntemlere kıyasla ana iş parçacığına kontrol sağlamanın hem daha kolay hem de daha iyi bir yolunu sunan yeni bir öneridir.

Getiri

JavaScript, görevleri yürütmek için tamamlamayı çalıştırma modelini kullanır. Bu durum, bir görevin ana iş parçacığında çalışması durumunda söz konusu görevin tamamlanması için gereken süre boyunca çalışacağı anlamına gelir. Bir görevin tamamlanmasının ardından kontrol, ana iş parçacığına geri getirilir. Bu, ana iş parçacığının sıradaki bir sonraki görevi işlemesine olanak tanır.

Bir görevin hiç bitmediği aşırı durumların (ör. sonsuz bir döngü) yanı sıra getiri, JavaScript görev planlama mantığının kaçınılmaz bir yönüdür. Her şey olacaktır, her şeyin ne zaman olacağı önemli değildir ve bir an önce gerçekleşecektir. Görevlerin çalıştırılması çok uzun sürdüğünde (tam olarak 50 milisaniyeden uzun) uzun görevler olarak kabul edilir.

Uzun görevler, tarayıcının kullanıcı girişine yanıt vermesini geciktirdiği için sayfaların yanıt verme performansını olumsuz etkiler. Görevler ne kadar sık gerçekleşir ve ne kadar uzun sürerse kullanıcıların sayfanın yavaşladığı, hatta tamamen bozulmuş olduğu izlenimi edinme olasılığı da o kadar artar.

Bununla birlikte, kodunuzun tarayıcıda bir görev başlatması, kontrol ana iş parçacığına geri verilmeden önce bu görevin tamamlanmasını beklemeniz gerektiği anlamına gelmez. Bir görevde açıkça veri vererek sayfadaki kullanıcı girişine duyarlılığı artırabilirsiniz. Bu, bir sonraki uygun fırsatta görevi bitecek şekilde böler. Bu sayede diğer görevler, uzun görevlerin tamamlanmasını beklemek zorunda kalmadan ana iş parçacığında daha erken zaman kazanabilir.

Görevleri ayırmanın girişlere daha iyi yanıt vermeyi nasıl kolaylaştırabileceğinin tasviri. Üst kısımda, uzun bir görev, görev tamamlanana kadar bir etkinlik işleyicinin çalışmasını engelliyor. En altta, parçalanmış görev, etkinlik işleyicinin normalde olacağından daha erken çalışmasına olanak tanır.
Kontrolü ana iş parçacığına geri vermeyi gösteren bir görselleştirme. En üstte, verim yalnızca görev tamamlanmaya başladıktan sonra gerçekleşir. Yani, kontrolü ana iş parçacığına geri döndürmeden önce görevlerin tamamlanması daha uzun sürebilir. En altta, uzun bir görev daha küçük görevlere bölünerek açık bir şekilde verim yapılır. Bu sayede kullanıcı etkileşimleri daha erken gerçekleşerek giriş duyarlılığını ve INP'yi iyileştirir.

Açık bir şekilde verim verdiğinizde, tarayıcıya "Hey, yapmak üzere olduğum işin biraz zaman alabileceğini anlıyorum 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ştiricinin araç kutusunda yer alan ve kullanıcı deneyimini iyileştirmeye yönelik oldukça faydalı bir araçtır.

Mevcut getiri stratejileriyle ilgili sorun

Yaygın bir getiri yöntemi, 0 zaman aşımı değeriyle setTimeout kullanır. Bu işe yarar, çünkü setTimeout işlevine iletilen geri çağırma, kalan çalışmayı daha sonra yürütme için sıraya alınacak ayrı bir göreve taşıyacaktır. Tarayıcının kendi kendine çalışmasını beklemek yerine, "bu büyük işi parçaya küçük parçalara ayıralım" diyorsunuz.

Bununla birlikte, setTimeout ile veri sağlamanın istenmeyen bir yan etkisi olabilir: Getiri noktasından sonra gelen çalışma, görev sırasının arkasına gider. Kullanıcı etkileşimlerine göre planlanan görevler de olması gerektiği gibi sırada en başta yer almaya devam eder. Ancak açık bir şekilde yapıldıktan sonra yapmak istediğiniz geri kalan işler, öncesinde sıraya alınmış rakip kaynaklardan gelen diğer görevler nedeniyle daha da gecikebilir.

Bu özelliğin nasıl uygulandığını görmek için bu Glitch demosunu deneyin veya aşağıdaki yerleşik sürümde deneyin. Demo, tıklayabileceğiniz birkaç düğme ve bunların altında görevler yürütüldüğünde günlüğe kaydedilen bir kutudan oluşur. Sayfaya geldiğinizde aşağıdaki işlemleri gerçekleştirin:

  1. Üstteki Görevleri düzenli olarak çalıştır etiketli düğmeyi tıklayarak, engelleme görevlerini düzenli aralıklarla çalışacak şekilde planlayabilirsiniz. Bu düğmeyi tıkladığınızda görev günlüğü, setInterval ile engelleme görevi çalıştırıldı ifadesini içeren çeşitli mesajlarla doldurulur.
  2. Daha sonra, Döngüyü çalıştır (her yinelemede setTimeout sonucunu verir) etiketli düğmeyi tıklayın.

Demonun alt kısmındaki kutuda şuna benzer bir metin göreceksiniz:

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ışta "görev sırasının sonu" gösterilmektedir setTimeout ile getiri elde edilirken ortaya çıkan davranıştır. Beş öğeyi çalıştıran döngüde beş öğe işlenir ve her biri işlendikten sonra setTimeout değerine sahip olur.

Bu, web'de sık karşılaşılan bir sorunu göstermektedir: Bir komut dosyasının (özellikle de üçüncü taraf bir komut dosyası), belirli bir aralıkta çalışma çalıştıran bir kronometre işlevi kaydetmesi için olağan dışı bir durum değildir. "Görev sırasının sonu" setTimeout ile birlikte gelen bir davranış ise diğer görev kaynaklarından gelen çalışmaların, döngünün teslim ettikten sonra yapması gereken geri kalan işlerden önce sıraya alınabileceği anlamına gelir.

Uygulamanıza bağlı olarak bu istenen bir sonuç olabilir veya olmayabilir. Ancak çoğu durumda, geliştiricilerin ana iş parçacığının kontrolünü bu kadar kolay bir şekilde bırakmak istememelerinin nedeni budur. Kullanıcı etkileşimleri daha erken gerçekleşebileceği için getiri iyidir ancak kullanıcı dışı diğer etkileşimlerle de ana iş parçacığında zaman kazanılmasına olanak tanır. Bu gerçekten ciddi bir sorun ama scheduler.yield çözümüne yardımcı olabilir.

scheduler.yield girişinden girin

scheduler.yield, Chrome'un 115 sürümünden beri deneysel web platformu özelliği olarak kullanılmaktadır. Aklınıza takılan sorulardan biri şudur: "setTimeout zaten şunu yaparken getirilmesi için özel bir işleve neden ihtiyacım var?"

Getirinin setTimeout için bir tasarım hedefi değil, 0 zaman aşımı değeri belirtilmiş olsa bile bir geri çağırma ileride daha sonraki bir zamanda çalışacak şekilde planlamanın güzel bir yan etkisi olduğunu belirtmek isteriz. Ancak unutulmaması gereken daha önemli nokta, setTimeout ile sonuç vermenin kalan çalışmaları görev sırasının geri gönderdiğidir. Varsayılan olarak scheduler.yield, kalan çalışmayı sıranın önüne gönderir. Diğer bir deyişle, çıktıktan hemen sonra devam ettirmek istediğiniz işin diğer kaynaklardaki görevlerde arka planda kalmayacağı (kullanıcı etkileşimlerinin önemli istisnaları dışında) anlamına gelir.

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

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

  // Yield!
  await scheduler.yield();

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

scheduler.yield adlı uygulamanın nasıl çalıştığını görmek için aşağıdakileri 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. Üstteki Görevleri düzenli olarak çalıştır etiketli düğmeyi tıklayın.
  5. Son olarak, Döngüyü çalıştır (her yinelemede scheduler.yield sonucunu verir) etiketli düğmeyi tıklayın.

Sayfanın alt kısmındaki kutuda yer alan çıkış şu şekilde 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 kullanılarak elde edilen demonun aksine, döngünün (her iterasyondan sonra verilmeye rağmen) kalan çalışmayı sıranın arkasına değil, önüne gönderdiğini görebilirsiniz. Bu, her iki yöntemin de en iyisini sağlar: Web sitenizde giriş duyarlılığını artırabilir, ancak aynı zamanda bitirmek istediğiniz çalışmanın getiri sonra verilmediğinden de emin olabilirsiniz.

Mutlaka deneyin!

scheduler.yield ilginizi çekiyorsa ve denemek istiyorsanız, Chrome'un 115 sürümünden itibaren bunu iki şekilde yapabilirsiniz:

  1. scheduler.yield ile yerel olarak deneme yapmak istiyorsanız Chrome'un adres çubuğuna chrome://flags yazıp buraya yazın ve Deneysel Web Platformu Özellikleri bölümündeki açılır listeden Etkinleştir'i seçin. Bu işlem, scheduler.yield ürününün (ve diğer tüm deneysel özelliklerin) yalnızca sizin Chrome örneğinizde kullanılabilmesini sağlar.
  2. Herkesin erişebileceği bir kaynaktaki gerçek Chromium kullanıcıları için scheduler.yield hizmetini etkinleştirmek isterseniz scheduler.yield kaynak denemesine kaydolmanız gerekir. Bu sayede, önerilen özelliklerle belirli bir süre için güvenli bir şekilde denemeler yapabilir ve Chrome Ekibi'ne, bu özelliklerin alanda nasıl kullanıldığına dair değerli bilgiler sağlayabilirsiniz. Kaynak denemelerinin işleyiş şekli hakkında daha fazla bilgi için bu rehberi okuyun.

scheduler.yield ürününü kullanmayan tarayıcıları desteklemeye devam ederken bu hizmeti nasıl kullanacağınız, hedeflerinize bağlıdır. Resmi çoklu dolguyu kullanabilirsiniz. Aşağıdakiler sizin için geçerliyse çoklu dolgu yararlıdır:

  1. Görevleri planlamak için uygulamanızda zaten scheduler.postTask kullanıyorsunuz.
  2. Görevleri ve getiri önceliklerini belirleyebilmek istiyorsunuz.
  3. scheduler.postTask API'nin 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 göre olmayabilir. Bu durumda, kendi yedeğinizi birkaç şekilde alabilirsiniz. İlk yaklaşım, mevcutsa scheduler.yield kullanır ancak yoksa setTimeout değerine 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 edebileceğiniz gibi, scheduler.yield özelliğini desteklemeyen tarayıcılar "sıranın önü" olmadan veri verecektir gösterir. Bu, hiç getiri elde etmek istemediğiniz anlamına geliyorsa kullanılabiliyorsa scheduler.yield kullanan ancak yoksa hiç getiri sağlamayan 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, Scheduler API için heyecan verici bir katkıdır. Bu API'nin, geliştiricilerin mevcut getiri stratejilerine kıyasla yanıt vermeyi iyileştirmelerini kolaylaştıracağını umuyoruz. scheduler.yield API'nin kullanışlı olduğunu düşünüyorsanız API'nin iyileştirilmesine yardımcı olmak için lütfen araştırmamıza katılın ve nasıl daha da geliştirilebileceği konusunda geri bildirimde bulunun.

Jonathan Allison'ın Unsplash'teki hero resim.