isInputPending() ile daha iyi JS planlaması

Yükleme performansı ile giriş duyarlılığı arasındaki dengeyi sağlamanıza yardımcı olabilecek yeni bir JavaScript API'si.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Hızlı yükleme yapmak zordur. İçeriklerini oluşturmak için JS'den yararlanan sitelerin şu anda yükleme performansı ile giriş duyarlılığı arasında bir denge kurması gerekiyor: Görüntüleme için gereken tüm işlemleri tek seferde gerçekleştirebilir (daha iyi yükleme performansı, daha kötü giriş duyarlılığı) veya girişe ve boyamaya duyarlı kalabilmek için işi daha küçük görevlere bölebilir (daha kötü yükleme performansı, daha iyi giriş duyarlılığı).

Bu dengenin sağlanmasına gerek kalmaması için Facebook, performanstan ödün vermeden yanıt vermeyi iyileştirmek amacıyla Chromium'da isInputPending() API'yi önerdi ve uyguladı. Kaynak denemesinden gelen geri bildirimler doğrultusunda API'de bir dizi güncelleme yaptık. API'nin artık Chromium 87'de varsayılan olarak kullanıma sunulduğunu duyurmaktan mutluluk duyuyoruz.

Tarayıcı uyumluluğu

Tarayıcı desteği

  • Chrome: 87.
  • Edge: 87.
  • Firefox: Desteklenmez.
  • Safari: Desteklenmez.

Kaynak

isInputPending(), Chromium tabanlı tarayıcıların 87 sürümünden itibaren gönderilir. Başka hiçbir tarayıcı, API'yi gönderme niyetini belirtmedi.

Arka plan

Günümüzün JS ekosistemindeki çalışmaların çoğu tek bir mesaj dizisinde (ana mesaj dizisi) yapılır. Bu, geliştiricilere güçlü bir yürütme modeli sağlar ancak komut dosyası uzun süre boyunca yürütülürse kullanıcı deneyimi (özellikle de yanıt verme hızı) büyük ölçüde etkilenebilir. Örneğin, bir giriş etkinliği tetiklenirken sayfa çok fazla işlem yapıyorsa sayfa, bu işlem tamamlanana kadar tıklama giriş etkinliğini işlemez.

Şu anda en iyi uygulama, JavaScript'i daha küçük bloklara ayırarak bu sorunla baş etmektir. Sayfa yüklenirken biraz JavaScript çalıştırabilir ve ardından kontrolü tarayıcıya geri verebilir. Tarayıcı daha sonra giriş etkinliği sırasını kontrol edip sayfaya bildirmesi gereken bir şey olup olmadığını görebilir. Ardından tarayıcı, JavaScript blokları eklendikçe bunları çalıştırmaya geri dönebilir. Bu, sorunun çözülmesine yardımcı olsa da başka sorunlara yol açabilir.

Sayfa kontrolü tarayıcıya her geri verdiğinde, tarayıcının giriş etkinliği kuyruğunu kontrol etmesi, etkinlikleri işlemesi ve sonraki JavaScript bloğunu alması biraz zaman alır. Tarayıcı etkinliklere daha hızlı yanıt verse de sayfanın genel yükleme süresi yavaşlar. Çok sık teslim olursak sayfa çok yavaş yüklenir. Daha seyrek izin verirsek tarayıcının kullanıcı etkinliklerine yanıt vermesi daha uzun sürer ve kullanıcılar can sıkıcı bir durumla karşılaşır. Eğlenceli değil.

Uzun JS görevlerini çalıştırdığınızda tarayıcının etkinlikleri göndermek için daha az zamanı olduğunu gösteren bir şema.

Facebook'da, bu can sıkıcı dengeyi ortadan kaldıracak yeni bir yükleme yaklaşımı geliştirdiğimizde neler olacağını görmek istedik. Bu konuda Chrome'daki arkadaşlarımızla iletişime geçtik ve isInputPending() önerisini ortaya çıkardık. isInputPending() API, web'de kullanıcı girişleri için kesinti kavramını kullanan ilk API'dir ve JavaScript'in tarayıcıya vermeden girişleri kontrol etmesine olanak tanır.

isInputPending() işlevinin, JS'nizin yürütmeyi tarayıcıya tamamen devretmeden bekleyen kullanıcı girişi olup olmadığını kontrol etmesine olanak tanıdığını gösteren bir şema.

API'ye ilgi gösterildiği için özelliği Chromium'da uygulamak ve kullanıma sunmak üzere Chrome'daki iş arkadaşlarımızla birlikte çalıştık. Chrome mühendislerinin yardımıyla, kaynak denemesi (Chrome'un bir API'yi tam olarak yayınlamadan önce değişiklikleri test edip geliştiricilerden geri bildirim almasının bir yoludur) sonrasında yamaları kullanıma sunduk.

Ardından, kaynak denemesinden ve W3C Web Performansı Çalışma Grubu'nun diğer üyelerinden geri bildirim aldık ve API'de değişiklikler yaptık.

Örnek: getiri planlayıcı

Sayfanızı yüklemek için ekranı engelleyen bir dizi işlem yapmanız gerektiğini varsayalım. Örneğin, bileşenlerden işaretleme oluşturma, asal sayıları çıkarma veya sadece havalı bir yükleme spinner'ı çizme. Bunların her biri ayrı bir çalışma öğesine ayrılır. Planlayıcı kalıbını kullanarak, varsayımsal bir processWorkQueue() işlevinde işimizi nasıl işleyebileceğimizi özetleyelim:

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

processWorkQueue() işlevini daha sonra setTimeout() aracılığıyla yeni bir makro görevde çağırarak tarayıcıya, nispeten kesintisiz bir şekilde çalışmaya devam ederken girişe biraz duyarlı olma (çalışma devam etmeden önce etkinlik işleyicileri çalıştırabilir) olanağı tanırız. Ancak, etkinlik döngüsünün kontrolünü isteyen diğer işler tarafından uzun süre boyunca planımızdan çıkarılabiliriz veya QUANTUM milisaniye ek etkinlik gecikmesi yaşayabiliriz.

Bu iyi, ancak daha iyisini yapabilir miyiz? Kesinlikle!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

navigator.scheduling.isInputPending() çağrısı ekleyerek, girişe daha hızlı yanıt verirken ekranı engelleme çalışmamızın kesintisiz şekilde yürütülmesini sağlayabiliriz. Çalışma tamamlanana kadar giriş dışında bir işlem (ör. boyama) yapmak istemiyorsanız QUANTUM uzunluğunu da kolayca artırabilirsiniz.

Varsayılan olarak, isInputPending() kaynağından "sürekli" etkinlikler döndürülmez. Bunlara mousemove, pointermove ve diğerleri dahildir. Bu videolar için de gelir elde etmek istiyorsanız sorun değil. includeContinuous özelliğini true olarak ayarlayarak isInputPending() için bir nesne sağladığımızda hazırız demektir:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

İşte bu kadar. React gibi çerçeveler, benzer mantık kullanarak temel planlama kitaplıklarına isInputPending() desteği ekliyor. Bu sayede, bu çerçeveleri kullanan geliştiricilerin önemli ölçüde yeniden yazma işlemi yapmadan isInputPending()'ten yararlanabilmesini umuyoruz.

Geri çekilmek her zaman kötü değildir

Daha az verim elde etmenin her kullanım alanı için doğru çözüm olmadığını belirtmek isteriz. Giriş etkinliklerini işleme dışında, kontrolü tarayıcıya döndürmenin birçok nedeni vardır. Örneğin, sayfayı oluşturmak ve sayfadaki diğer komut dosyalarını yürütmek için de kontrolü tarayıcıya döndürebilirsiniz.

Tarayıcının, bekleyen giriş etkinliklerini doğru şekilde ilişkilendiremediği durumlar vardır. Özellikle, kaynak ötesi iFrame'ler için karmaşık klipler ve maskeler ayarlamak yanlış negatif raporlayabilir (yani isInputPending() bu çerçeveleri hedeflerken beklenmedik bir şekilde yanlış döndürebilir). Sitenizin stilize alt çerçevelerle etkileşim gerektirmesi durumunda yeterince sık verim verdiğinizden emin olun.

Etkinlik döngüsü paylaşan diğer sayfalara da dikkat edin. Android için Chrome gibi platformlarda, birden fazla kaynağın bir etkinlik döngüsünü paylaşması oldukça yaygındır. Giriş, kaynak ötesi bir çerçeveye dağıtılırsa isInputPending() hiçbir zaman true değerini döndürmez. Bu nedenle, arka plandaki sayfalar ön plandaki sayfaların duyarlılığını etkileyebilir. Sayfa Görünürlük API'sini kullanarak arka planda çalışırken daha sık azaltmak, ertelemek veya vermeyi tercih edebilirsiniz.

isInputPending() değerini dikkatli bir şekilde kullanmanızı öneririz. Kullanıcıyı engelleyen bir işlem yapmanız gerekmiyorsa daha sık sonuç vererek etkinlik döngüsündeki diğer kullanıcılara yardımcı olun. Uzun görevler zararlı olabilir.

Geri bildirim

  • is-input-pending deposunda spesifikasyonla ilgili geri bildirim bırakın.
  • Twitter'da @acomminos (özellik yazarlarından biri) ile iletişime geçin.

Sonuç

isInputPending()'ün kullanıma sunulması ve geliştiricilerin bu özelliği hemen kullanmaya başlayabilmesi bizi heyecanlandırıyor. Bu API, Facebook'ın ilk kez yeni bir web API'si oluşturup bu API'yi fikir aşamasından standart önerisine ve ardından bir tarayıcıda kullanıma sunma aşamasına taşımasıdır. Bu noktaya gelmemize yardımcı olan herkese teşekkür etmek ve bu fikri hayata geçirmemize ve kullanıma sunmamıza yardımcı olan Chrome ekibindeki herkese özel olarak teşekkür etmek isteriz.

Unsplash'taki Will H McMahan tarafından çekilen hero fotoğrafı.