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 Core Web Vital olarak First Input Delay (FID)'in yerini alacak.

Web geliştiricilerin web sitelerini olabildiğince hızlı hale getirmelerine yardımcı olacak yeni API'ler sunma çabalarını sürdüren Chrome Ekibi, şu anda Chrome'un 115 sürümünden itibaren scheduler.yield için kaynak deneme sürümü yayınlıyor. 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 işlemek için sonuna 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. Bu durum gerçekleşecektir. Tek sorun ne zaman gerçekleşeceğidir. Ne kadar erken olursa o kadar iyidir. 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 verme özelliğini geciktirdiği için sayfanın tepkisinin düşük olmasına neden olur. 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.

Ancak, kodunuzun tarayıcıda bir görevi başlatması, kontrolün ana iş parçacığına geri verilmesi için 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 geri verme işlemini gösteren görselleştirme. Üstte, yalnızca bir görev tamamlandıktan sonra verim sağlanır. Bu, görevlerin tamamlanmasının, kontrolü ana iş parçacığına geri döndürmeden önce 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 çalıştırılır. Bu da 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, setTimeout işlevine iletilen geri çağırma işlevi, kalan işi sonraki yürütme için sıraya eklenecek ayrı bir göreve taşıdığı için işe yarar. 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: Verim noktasından sonra gelen işler 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 bu düğmelerin 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üğüne setInterval ile engelleme görevi çalıştırıldı yazan birkaç mesaj eklenir.
  2. Ardından, Döngüyü çalıştır, her iterasyonda setTimeout ile sonuç ver 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österir: 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 115 sürümünden beri bir işaretin arkasında deneysel web platformu özelliği olarak kullanılabilir. "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 gelecekte daha sonraki bir noktada çalışacak şekilde bir geri çağırma planlamanın güzel bir yan etkisi olduğunu belirtmek gerekir. Ancak daha da önemlisi, setTimeout ile teslim vermenin kalan işi görev kuyruğunun arkasına göndermesidir. Varsayılan olarak scheduler.yield, kalan işi kuyruğun başına gönderir. Bu, devretme işleminden hemen sonra devam etmek istediğiniz işin, diğer kaynaklardan gelen görevlere (kullanıcı etkileşimleri hariç) göre ikinci plana atılmayacağı anlamına gelir.

scheduler.yield, ana mesaj dizisine yol veren ve çağrıldığında bir 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. Görevleri düzenli olarak çalıştır etiketli en üstteki düğmeyi tıklayın.
  5. Son olarak, Döngüyü çalıştır, her iterasyonda scheduler.yield ile sonuç ver 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 yield yapan demodakinin aksine, döngünün her iterasyondan sonra yield vermesine rağmen kalan işi kuyruğun arkasına değil, önüne gönderdiğini görebilirsiniz. Bu sayede iki dünyanın da en iyisini elde edebilirsiniz: Web sitenizdeki giriş duyarlılığını artırmak için teslim olabilir, ancak teslim sonrasında tamamlamak istediğiniz işin gecikmesini önleyebilirsiniz.

Mutlaka deneyin!

scheduler.yield ilginizi çekiyorsa ve denemek istiyorsanız Chrome 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. Uygulamanızda görevleri planlamak için zaten scheduler.postTask kullanıyorsanız.
  2. Görev ve verim öncelikleri belirleyebilmek istiyorsunuz.
  3. scheduler.postTask API'sinin sunduğu TaskController sınıfı aracılığıyla görevleri iptal etmek veya yeniden önceliklendirmek istiyorsunuz.

Bu durum sizin durumunuz değilse polyfill sizin için 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 yöntem işe yarayabilir ancak tahmin edebileceğiniz gibi, scheduler.yield özelliğini desteklemeyen tarayıcılar "sıranın başında" davranışı olmadan sonuç verir. Bu, hiç verim elde etmek istemediğiniz anlamına geliyorsa mevcutsa scheduler.yield kullanan ancak mevcut değilse hiç verim 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 sizin için yararlı bir API gibi görünüyorsa lütfen API'yi iyileştirmemize yardımcı olmak için araştırmamıza katılın ve nasıl daha da iyileştirilebileceğiyle ilgili geri bildirimde bulunun.

Jonathan Allison tarafından Unsplash'tan alınan lokomotif resim.