requestIdleCallback'i kullanma

Paul Lewis

Birçok site ve uygulamada çalıştırılacak çok sayıda komut dosyası vardır. JavaScript'inizin genellikle en kısa sürede çalıştırılması gerekir ancak aynı zamanda kullanıcının yoluna çıkmasını istemezsiniz. Kullanıcı sayfayı kaydırırken analiz verilerini gönderirseniz veya kullanıcı düğmeye dokunurken DOM'a öğe eklerseniz web uygulamanız yanıt vermeyebilir ve bu da kötü bir kullanıcı deneyimine neden olabilir.

Gerekli olmayan işleri planlamak için requestIdleCallback işlevini kullanma.

Neyse ki artık size yardımcı olabilecek bir API var: requestIdleCallback. requestAnimationFrame'ü kullanmaya başlamamız, animasyonları düzgün bir şekilde planlamamıza ve 60 fps'ye ulaşma şansımızı en üst düzeye çıkarmamıza olanak tanıdığı gibi requestIdleCallback da bir karenin sonunda boş zaman olduğunda veya kullanıcı etkin olmadığında işleri planlar. Bu, kullanıcının yoluna çıkmadan işinizi yapma fırsatı olduğu anlamına gelir. Chrome 47'den itibaren kullanıma sunulduğundan Chrome Canary'i kullanarak hemen deneyebilirsiniz. Bu deneme aşamasındaki bir özelliktir ve spesifikasyon hâlâ değişmektedir. Bu nedenle, gelecekte bazı değişiklikler olabilir.

Neden requestIdleCallback işlevini kullanmalıyım?

Temel olmayan işleri kendiniz planlamak çok zordur. requestAnimationFrame geri çağırma işlevleri çalıştıktan sonra stil hesaplamaları, düzen, boyama ve çalıştırılması gereken diğer tarayıcı iç işlevleri olduğundan tam olarak ne kadar kare süresinin kaldığını anlamak mümkün değildir. Şirket içinde geliştirilen bir çözüm, bunların hiçbirini hesaba katamaz. Kullanıcının herhangi bir şekilde etkileşimde bulunmadığından emin olmak için, işlevsellik için bunlara ihtiyacınız olmasa bile her tür etkileşim etkinliğine (scroll, touch, click) dinleyici eklemeniz gerekir. Bu, kullanıcının etkileşimde bulunmadığından emin olmanız için yalnızca gereklidir. Öte yandan tarayıcı, çerçevenin sonunda ne kadar süre kaldığını ve kullanıcının etkileşimde bulunup bulunmadığını tam olarak bilir. Bu nedenle, requestIdleCallback aracılığıyla boş zamanı mümkün olan en verimli şekilde kullanmamıza olanak tanıyan bir API elde ederiz.

Bu konuyu biraz daha ayrıntılı bir şekilde inceleyip nasıl yararlanabileceğimize bakalım.

requestIdleCallback olup olmadığını kontrol etme

requestIdleCallback henüz kullanıma yeni sunulduğundan kullanmadan önce kullanılabilir olup olmadığını kontrol etmeniz gerekir:

if ('requestIdleCallback' in window) {
    // Use requestIdleCallback to schedule work.
} else {
    // Do what you’d do today.
}

Ayrıca davranışını değiştirebilirsiniz. Bunun için setTimeout değerine geri dönmeniz gerekir:

window.requestIdleCallback =
    window.requestIdleCallback ||
    function (cb) {
    var start = Date.now();
    return setTimeout(function () {
        cb({
        didTimeout: false,
        timeRemaining: function () {
            return Math.max(0, 50 - (Date.now() - start));
        }
        });
    }, 1);
    }

window.cancelIdleCallback =
    window.cancelIdleCallback ||
    function (id) {
    clearTimeout(id);
    }

setTimeout, requestIdleCallback gibi boşta kalma süresini bilmediği için kullanmak pek iyi bir fikir değildir ancak requestIdleCallback kullanılamıyorsa işlevinizi doğrudan çağıracağınız için bu şekilde bir yama kullanmaktan daha kötü bir durumla karşılaşmazsınız. requestIdleCallback kullanılabilir durumdaysa bu dolgu sayesinde aramalarınız sessizce yönlendirilir. Bu da çok iyi bir durumdur.

Şimdilik bu özelliğin var olduğunu varsayalım.

requestIdleCallback işlevini kullanma

requestIdleCallback'ü çağırmak, ilk parametresi olarak bir geri çağırma işlevi aldığı için requestAnimationFrame'e çok benzer:

requestIdleCallback(myNonEssentialWork);

myNonEssentialWork çağrıldığında, deadline nesnesi verilir. Bu nesne, işinize ne kadar zaman kaldığını belirten bir sayı döndüren bir işlev içerir:

function myNonEssentialWork (deadline) {
    while (deadline.timeRemaining() > 0)
    doWorkIfNeeded();
}

En son değeri almak için timeRemaining işlevi çağrılabilir. timeRemaining() sıfır döndürdüğünde, daha fazla işiniz varsa başka bir requestIdleCallback planlayabilirsiniz:

function myNonEssentialWork (deadline) {
    while (deadline.timeRemaining() > 0 && tasks.length > 0)
    doWorkIfNeeded();

    if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

İşlevinizin çağrılmasını garanti etme

Çok yoğunsanız ne yaparsınız? Geri aranmayacağınızdan endişe duyabilirsiniz. requestIdleCallback, requestAnimationFrame'e benzese de isteğe bağlı ikinci bir parametre almasıyla da farklıdır: zaman aşımı özelliğine sahip bir seçenekler nesnesi. Ayarlanırsa bu zaman aşımı, tarayıcıya geri çağırma işlevini milisaniye cinsinden yürütmesi gereken bir süre verir:

// Wait at most two seconds before processing events.
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });

Geri çağırma işleviniz zaman aşımı nedeniyle tetiklenirse iki şey fark edersiniz:

  • timeRemaining() sıfır döndürür.
  • deadline nesnesinin didTimeout özelliği true olur.

didTimeout değerinin doğru olduğunu görürseniz büyük olasılıkla işi çalıştırıp bitirmek istersiniz:

function myNonEssentialWork (deadline) {

    // Use any remaining time, or, if timed out, just run through the tasks.
    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
            tasks.length > 0)
    doWorkIfNeeded();

    if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

Bu zaman aşımı, kullanıcılarınıza neden olabileceği olası kesinti nedeniyle (çalışma, uygulamanızın yanıt vermemesi veya takılmasına neden olabilir) bu parametreyi ayarlarken dikkatli olun. Mümkün olduğunda geri aramanın ne zaman yapılacağına tarayıcının karar vermesine izin verin.

Analytics verilerini göndermek için requestIdleCallback işlevini kullanma

Analiz verilerini göndermek için requestIdleCallback'ü kullanmaya göz atalım. Bu durumda, muhtemelen bir gezinme menüsüne dokunma gibi bir etkinliği izlemek isteriz. Ancak bu öğeler genellikle ekranda animasyonlu olarak göründüğü için bu etkinliği Google Analytics'e hemen göndermekten kaçınmak isteriz. Gönderilecek bir dizi etkinlik oluşturur ve bunların gelecekte bir noktada gönderilmesini isteriz:

var eventsToSend = [];

function onNavOpenClick () {

    // Animate the menu.
    menu.classList.add('open');

    // Store the event for later.
    eventsToSend.push(
    {
        category: 'button',
        action: 'click',
        label: 'nav',
        value: 'open'
    });

    schedulePendingEvents();
}

Şimdi, bekleyen etkinlikleri işlemek için requestIdleCallback işlevini kullanmamız gerekiyor:

function schedulePendingEvents() {

    // Only schedule the rIC if one has not already been set.
    if (isRequestIdleCallbackScheduled)
    return;

    isRequestIdleCallbackScheduled = true;

    if ('requestIdleCallback' in window) {
    // Wait at most two seconds before processing events.
    requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });
    } else {
    processPendingAnalyticsEvents();
    }
}

Burada 2 saniyelik bir zaman aşımı ayarladığımı görebilirsiniz ancak bu değer uygulamanıza bağlıdır. Analytics verileri için, verilerin yalnızca gelecekteki bir noktada değil, makul bir zaman aralığında raporlanmasını sağlamak amacıyla bir zaman aşımı kullanılması mantıklıdır.

Son olarak, requestIdleCallback işlevinin yürüteceği işlevi yazmamız gerekir.

function processPendingAnalyticsEvents (deadline) {

    // Reset the boolean so future rICs can be set.
    isRequestIdleCallbackScheduled = false;

    // If there is no deadline, just run as long as necessary.
    // This will be the case if requestIdleCallback doesn’t exist.
    if (typeof deadline === 'undefined')
    deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

    // Go for as long as there is time remaining and work to do.
    while (deadline.timeRemaining() > 0 && eventsToSend.length > 0) {
    var evt = eventsToSend.pop();

    ga('send', 'event',
        evt.category,
        evt.action,
        evt.label,
        evt.value);
    }

    // Check if there are more events still to send.
    if (eventsToSend.length > 0)
    schedulePendingEvents();
}

Bu örnekte, requestIdleCallback yoksa analiz verilerinin hemen gönderilmesi gerektiğini varsayıyorum. Ancak üretim uygulamasında, herhangi bir etkileşimle çakışmaması ve takılmalara neden olmaması için gönderimi bir zaman aşımıyla ertelemek daha iyi olabilir.

DOM değişiklikleri yapmak için requestIdleCallback işlevini kullanma

requestIdleCallback'ün performansa gerçekten yardımcı olabileceği bir diğer durum da, sürekli büyüyen ve gecikmeli olarak yüklenen bir listenin sonuna öğe eklemek gibi zorunlu olmayan DOM değişiklikleri yapmanız gerektiğinde ortaya çıkar. requestIdleCallback'ün tipik bir çerçeveye nasıl sığdırıldığını inceleyelim.

Tipik bir çerçeve.

Tarayıcının, belirli bir karede geri çağırma işlevini çalıştıracak kadar meşgul olması mümkündür. Bu nedenle, bir karenin sonunda daha fazla iş yapmak için herhangi bir boş zaman olacağını beklememelisiniz. Bu, kare başına çalıştırılan setImmediate gibi bir işlevden farklıdır.

Geri çağırma, çerçevenin sonunda tetiklenirse geçerli çerçeve bağlandıktan sonra tetiklenecek şekilde planlanır. Bu, stil değişikliklerinin uygulandığı ve daha da önemlisi düzenin hesaplandığı anlamına gelir. Boş durumda geri çağırma içinde DOM değişiklikleri yaparsak bu düzen hesaplamaları geçersiz olur. Bir sonraki karede herhangi bir türde düzen okuması (ör. getBoundingClientRect, clientWidth vb.) varsa tarayıcının zorunlu senkronize düzen gerçekleştirmesi gerekir. Bu, performansta olası bir darboğazdır.

DOM değişikliklerini boşta kalma geri çağırmasında tetiklememenin bir diğer nedeni de DOM'u değiştirmenin zaman üzerindeki etkisinin tahmin edilemez olmasıdır. Bu nedenle, tarayıcı tarafından sağlanan son tarihi kolayca aşabiliriz.

En iyi uygulama, tarayıcı tarafından bu tür çalışmalar göz önünde bulundurularak planlandığından, DOM değişikliklerini yalnızca bir requestAnimationFrame geri çağırma işlevi içinde yapmaktır. Bu nedenle, kodumuzun bir doküman parçası kullanması gerekir. Bu doküman parçası, sonraki requestAnimationFrame geri çağırma işlevine eklenebilir. VDOM kitaplığı kullanıyorsanız değişiklik yapmak için requestIdleCallback'ü kullanırsınız ancak DOM yamalarını boşta kalma geri çağırımında değil, sonraki requestAnimationFrame geri çağırımında uygulayabilirsiniz.

Bu nedenle, koda göz atalım:

function processPendingElements (deadline) {

    // If there is no deadline, just run as long as necessary.
    if (typeof deadline === 'undefined')
    deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

    if (!documentFragment)
    documentFragment = document.createDocumentFragment();

    // Go for as long as there is time remaining and work to do.
    while (deadline.timeRemaining() > 0 && elementsToAdd.length > 0) {

    // Create the element.
    var elToAdd = elementsToAdd.pop();
    var el = document.createElement(elToAdd.tag);
    el.textContent = elToAdd.content;

    // Add it to the fragment.
    documentFragment.appendChild(el);

    // Don't append to the document immediately, wait for the next
    // requestAnimationFrame callback.
    scheduleVisualUpdateIfNeeded();
    }

    // Check if there are more events still to send.
    if (elementsToAdd.length > 0)
    scheduleElementCreation();
}

Burada öğeyi oluşturup doldurmak için textContent mülkünü kullanıyorum ancak öğe oluşturma kodunuz daha karmaşık olabilir. Öğe oluşturulduktan sonra scheduleVisualUpdateIfNeeded çağrılır. Bu çağrı, doküman parçasını gövdeye ekleyeceği tek bir requestAnimationFrame geri çağırma işlevi oluşturur:

function scheduleVisualUpdateIfNeeded() {

    if (isVisualUpdateScheduled)
    return;

    isVisualUpdateScheduled = true;

    requestAnimationFrame(appendDocumentFragment);
}

function appendDocumentFragment() {
    // Append the fragment and reset.
    document.body.appendChild(documentFragment);
    documentFragment = null;
}

Her şey yolunda giderse DOM'a öğe eklerken çok daha az takılma göreceğiz. Mükemmel!

SSS

  • Polifill var mı? Maalesef değil. Ancak setTimeout adresine şeffaf bir yönlendirme yapmak istiyorsanız bir ara yazılım kullanabilirsiniz. Bu API'nin var olmasının nedeni, web platformunda çok gerçek bir boşluğu doldurmasıdır. Etkinlik eksikliğini tahmin etmek zordur ancak çerçevenin sonunda boş zamanın ne kadar olduğunu belirleyen bir JavaScript API'si olmadığından en iyi ihtimalle tahminde bulunmanız gerekir. setTimeout, setInterval veya setImmediate gibi API'ler iş planlamak için kullanılabilir ancak requestIdleCallback gibi kullanıcı etkileşimini önlemek için zamanlanmazlar.
  • Son tarihi aşarsam ne olur? timeRemaining() sıfır döndürür ancak daha uzun süre çalıştırmayı tercih ederseniz bunu, tarayıcı çalışmanızı durduracağından korkmadan yapabilirsiniz. Ancak tarayıcı, kullanıcılarınıza sorunsuz bir deneyim sunmak için denemeniz gereken son tarihi size bildirir. Bu nedenle, çok iyi bir nedeniniz yoksa son tarihe her zaman uymanız gerekir.
  • timeRemaining() işlevinin döndüreceği maksimum değer var mı? Evet, şu anda 50 ms. Duyarlı bir uygulama sürdürmeye çalışırken kullanıcı etkileşimlerine verilen tüm yanıtlar 100 ms'nin altında tutulmalıdır. Kullanıcı etkileşimde bulunursa 50 mslik zaman aralığı, çoğu durumda boşta kalma geri çağırma işlevinin tamamlanmasına ve tarayıcının kullanıcının etkileşimlerine yanıt vermesine olanak tanır. Arka arkaya planlanmış birden fazla boş zaman geri çağırma alabilirsiniz (tarayıcı, bunları çalıştırmak için yeterli zaman olduğunu belirlerse).
  • requestIdleCallback işlevinde yapmamam gereken bir işlem var mı? İdeal olarak, yaptığınız iş görece tahmin edilebilir özelliklere sahip küçük parçalara (mikro görevler) ayrılmalıdır. Örneğin, stil hesaplamalarını, düzeni, boyamayı ve kompozisyon oluşturmayı tetikleyeceğinden DOM'un değiştirilmesi, yürütme sürelerinin tahmin edilemez olmasına neden olur. Bu nedenle, DOM değişikliklerini yalnızca yukarıda önerildiği gibi bir requestAnimationFrame geri çağırma işlevinde yapmanız gerekir. Dikkat edilmesi gereken bir diğer nokta da Promise'leri çözme (veya reddetme) işlemidir. Geri çağırmalar, zaman kalmamış olsa bile boşta kalma geri çağırması sona erdikten hemen sonra yürütülür.
  • Her karenin sonunda requestIdleCallback işareti görecek miyim? Hayır, her zaman değil. Tarayıcı, bir çerçevenin sonunda veya kullanıcının etkin olmadığı dönemlerde geri çağırma işlevini planlar. Geri çağırma işlevinin her karede çağrılmasını beklememelisiniz. Geri çağırma işlevinin belirli bir zaman aralığında çalışmasını istiyorsanız zaman aşımından yararlanmanız gerekir.
  • Birden fazla requestIdleCallback geri çağırma alabilir miyim? Evet, birden fazla requestAnimationFrame geri araması yapabileceğiniz gibi bunu da yapabilirsiniz. Ancak, ilk geri aramanız geri arama sırasında kalan süreyi tüketirse diğer geri aramalar için zaman kalmayacağını unutmayın. Diğer geri çağırmaların çalıştırılabilmesi için tarayıcının bir sonraki boş durumunu beklemesi gerekir. Yapmaya çalıştığınız işe bağlı olarak, tek bir boş zaman geri çağırma işlemi oluşturmak ve işi orada bölmek daha iyi olabilir. Alternatif olarak, zaman aşımı özelliğini kullanarak geri çağırmaların zaman sıkıntısı yaşamadığından emin olabilirsiniz.
  • Başka bir işlevin içine yeni bir boşta kalma geri çağırma işlevi ayarlarsam ne olur? Yeni boşta kalma geri çağırması, mevcut çerçeve yerine sonraki çerçeveden itibaren en kısa sürede çalışacak şekilde planlanır.

Aktif değil.

requestIdleCallback, kullanıcının yoluna çıkmadan kodunuzu çalıştırabilmenizi sağlayan harika bir yöntemdir. Kullanımı basit ve çok esnektir. Ancak henüz çok erken bir aşamadayız ve spesifikasyon tam olarak belirlenmiş değil. Bu nedenle, geri bildirimlerinizi bekliyoruz.

Chrome Canary'da bu özelliği inceleyin, projelerinizi denemek için kullanın ve deneyimlerinizi bizimle paylaşın.