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 biridir. Chrome Ekibi, web geliştiricilerine bu konuda yardımcı olmak için yoğun şekilde çalışmaktadır. Bu yıl, Interaction to Next Paint (INP) metriğinin deneysel durumdan bekleme durumuna geçeceği duyuruldu. 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, planlayıcı API'sine önerilen yeni bir eklentidir. Bu eklenti, geleneksel olarak kullanılan yöntemlerden daha kolay ve daha iyi bir şekilde ana iş parçacığına kontrol vermeyi sağlar.

Verirken

JavaScript, görevleri yürütmek için tamamlamaya kadar çalıştırma modelini kullanır. Bu, bir görev ana iş parçacığında çalışırken tamamlanması için gereken süre boyunca çalıştığı anlamına gelir. Bir görevin tamamlanmasının ardından kontrol, ana iş parçacığına iade edilir. Bu sayede ana iş parçacığı, sıradaki görevi işleyebilir.

Bir görevin hiçbir zaman bitmediği aşırı durumlar (ör. sonsuz döngü) dışında, JavaScript'in görev planlama mantığının kaçınılmaz bir yönü, görevden vazgeçmektir. Her şey olacaktır, her şeyin ne zaman olacağı önemli değildir ve bir an önce gerçekleşecektir. Görevlerin çalışması ç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 sayfanın yanıt verme hızının düşük olmasına yol açar. Uzun görevler ne kadar sık gerçekleşirse ve ne kadar uzun sürerse kullanıcıların sayfanın yavaş olduğu veya tamamen bozuk olduğu izlenimini alma olasılığı 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 yield yaparak sayfadaki kullanıcı girişine verilen yanıtı iyileştirebilirsiniz. Bu işlem, görevi bir sonraki uygun fırsatta tamamlanacak ş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 alabilir.

Bir görevi bölmenin, giriş duyarlılığını nasıl artırabileceğini gösteren görsel. Üstte, uzun bir görev, görev tamamlanana kadar bir etkinlik işleyicinin çalışmasını engeller. Alt kısımda, parçalara ayrılmış görev, etkinlik işleyicinin normalden daha erken çalışmasına olanak tanır.
Kontrolü ana iş parçacığına devretmeyi gösteren görselleştirme. Üstte, yalnızca bir görev tamamlandıktan sonra verim sağlanır. Bu, kontrolü ana iş parçacığına geri döndürmeden önce görevlerin tamamlanmasının daha uzun sürebileceği anlamına gelir. Alt kısımda, uzun bir görev birkaç küçük göreve bölünerek açıkça veriliyor. Bu sayede kullanıcı etkileşimleri daha erken gerçekleşerek giriş duyarlılığını ve INP'yi iyileştirir.

Açıkça yield yaptığınızda tarayıcıya "Yapacağım 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ü yapmanız gerekmesini istemiyorum" demiş olursunuz. Geliştiricilerin araç kutusundaki bu değerli araç, kullanıcı deneyimini iyileştirme konusunda çok faydalı olabilir.

Mevcut getiri stratejileriyle ilgili sorun

Sonuç döndürmeyle ilgili yaygın bir yöntem, 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 teslim olmasını beklemek yerine, "Bu büyük iş parçasını daha küçük parçalara ayıralım" diyorsunuz.

Ancak setTimeout ile verim elde etmenin istenmeyen bir yan etkisi olabilir: Verimli noktanın sonrasında gelen iş, görev kuyruğunun sonuna gider. Kullanıcı etkileşimleriyle planlanan görevler, olması gerektiği gibi kuyruğun önüne geçer. Ancak açıkça verdikten sonra yapmak istediğiniz kalan iş, kendisinden önce kuyruğa eklenen rakip kaynaklardan gelen diğer görevler tarafından daha da gecikebilir.

Bu özelliğin nasıl çalıştığını görmek için bu Glitch demosunu deneyin veya aşağıdaki yerleşik sürümde denemeler yapın. Demo, tıklayabileceğiniz birkaç düğmeden ve bunların altında görevlerin ne zaman çalıştırıldığını kaydeden bir kutudan oluşur. Sayfaya geldiğinizde aşağıdaki işlemleri yapın:

  1. Görevleri düzenli olarak çalıştır etiketli en üstteki düğmeyi tıklayın. Bu düğme, engelleyen görevleri belirli aralıklarla çalıştıracak şekilde planlar. 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.

Demo'nun alt kısmındaki kutuda şuna benzer bir ifade görürsünüz:

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 çıktı, setTimeout ile yield kullanıldığında ortaya çıkan "görev kuyruğunun sonu" davranışını gösterir. Çalışan döngü beş öğeyi işler ve her biri işlendikten sonra setTimeout döndürür.

Bu, web'de yaygın bir sorunu göstermektedir: Bir komut dosyasının (özellikle üçüncü taraf komut dosyalarının) belirli aralıklarla çalışan bir zamanlayıcı işlevi kaydetmesi normaldir. setTimeout ile verim sağlamanın getirdiği "görev kuyruğunun sonu" davranışı, diğer görev kaynaklarından gelen işlerin, döngünün verim sağladıktan sonra yapması gereken kalan işin önüne geçebileceği anlamına gelir.

Bu, uygulamanıza bağlı olarak istenilen bir sonuç olabilir veya olmayabilir. Ancak çoğu durumda geliştiriciler, ana iş parçacığının kontrolünü bu kadar kolay bırakmak istemez. Kullanıcı etkileşimlerinin daha erken çalışma fırsatı bulduğu için verim verme iyidir ancak kullanıcı etkileşimi olmayan diğer çalışmaların da ana iş parçacığında zaman almasına olanak tanır. Bu gerçek bir sorundur ancak scheduler.yield bu sorunu çözmenize 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. "setTimeout zaten verim verirken neden özel bir işleve ihtiyacım var?" diye düşünebilirsiniz.

Yielding'in setTimeout'ün tasarım hedefi olmadığını, 0 zaman aşımı değeri belirtilmiş olsa bile geri çağırma işlevinin gelecekte daha ileri bir zamanda çalışacak şekilde planlanmasının güzel bir yan etkisi olduğunu belirtmek gerekir. Ancak setTimeout ile verirken kalan işin görev kuyruğunun arkasına gönderildiğini unutmayın. Varsayılan olarak scheduler.yield, kalan işi kuyruğun başına gönderir. Diğer bir deyişle, çıktıktan hemen sonra devam ettirmek istediğiniz iş, diğer kaynaklardaki görevlerde arka planda yer almayacaktır (kullanıcı etkileşimleri hariç).

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 kullanabilirsiniz:

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

  // Yield!
  await scheduler.yield();

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

scheduler.yield'ü çalışırken görmek için aşağıdakileri yapın:

  1. chrome://flags adresine gidin.
  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 şuna benzer bir çıktı gösterilir:

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 verim sağlayan demodakinin aksine, döngünün her iterasyondan sonra verim sağlamasına rağmen kalan işi kuyruğun 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 özelliğini yerel olarak denemek istiyorsanız Chrome'un adres çubuğuna chrome://flags yazın ve Deneysel Web Platformu Özellikleri bölümündeki açılır menüden Etkinleştir'i seçin. Bu durumda scheduler.yield (ve diğer deneysel özellikler) yalnızca Chrome örneğinizde kullanılabilir.
  2. Herkese açık bir kaynakta gerçek Chromium kullanıcıları için scheduler.yield'ü etkinleştirmek istiyorsanız scheduler.yield kaynak denemesine kaydolmanız gerekir. Bu sayede, önerilen özellikleri belirli bir süre boyunca güvenli bir şekilde deneyebilir ve Chrome Ekibi'ne bu özelliklerin sahada nasıl kullanıldığına dair değerli bilgiler sağlayabilirsiniz. Kaynak denemelerinin işleyiş şekli hakkında daha fazla bilgi için bu kılavuzu okuyun.

scheduler.yield'ü kullanma şekliniz (bu özelliği uygulamayan tarayıcıları desteklemeye devam ederken), hedeflerinize bağlıdır. Resmi polyfill'i kullanabilirsiniz. Aşağıdakiler durumunuz için geçerliyse polyfill faydalıdır:

  1. Görevleri planlamak için uygulamanızda zaten scheduler.postTask kullanıyorsunuz.
  2. Görev ve verim öncelikleri 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 uygun olmayabilir. Bu durumda, kendi yedek planınızı birkaç şekilde kullanabilirsiniz. İlk yaklaşım, varsa scheduler.yield değerini kullanır, yoksa 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 edebileceğiniz gibi, scheduler.yield özelliğini desteklemeyen tarayıcılar "sıranın önü" davranışı olmadan veri verir. 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, planlayıcı API'sine eklenen heyecan verici bir özelliktir. Bu özellik, geliştiricilerin mevcut verim stratejilerine kıyasla yanıt vermeyi iyileştirmesini kolaylaştıracaktır. 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 tarafından Unsplash'tan alınan lokomotif resim.