requestIdleCallback'i kullanma

Paul Lewis

Birçok sitenin ve uygulamanın yürütmesi gereken birçok komut dosyası vardır. JavaScript'inizin çoğu zaman mümkün olan en kısa sürede çalıştırılması gerekir, ancak aynı zamanda kullanıcının önüne geçmesini istemezsiniz. Kullanıcı sayfayı kaydırırken analiz verileri gönderirseniz veya kullanıcı düğmeye dokunurken DOM'a öğeler eklerseniz web uygulamanız yanıt vermeyebilir ve bu da kötü bir kullanıcı deneyimine neden olabilir.

Önemli olmayan işleri planlamak için requestIdleCallback kullanılıyor.

Neyse ki artık requestIdleCallback API'de size yardımcı olabilecek bir API var. requestAnimationFrame kullanımı, 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 bir karenin sonunda boş zaman olduğunda veya kullanıcı etkin olmadığında da çalışmayı planlar. Bu, kullanıcıyı engellemeden çalışma fırsatına sahip olduğu anlamına gelir. Chrome 47 sürümünden itibaren kullanıma sunulduğu için Chrome Canary'yi kullanarak uygulamayı hemen deneyebilirsiniz! Bu deneysel bir özelliktir ve spesifikasyon hâlâ değişmektedir, bu nedenle gelecekte işler değişebilir.

Neden requestIdleCallback kullanmalıyım?

Zorunlu olmayan işleri kendi başınıza planlamak çok zordur. Kalan kare süresini tam olarak belirlemek imkansızdır, çünkü requestAnimationFrame geri çağırma işlemi yürütüldükten sonra stil hesaplamaları, düzen, boya ve tarayıcı içinde çalışması gereken diğer dahili öğeler vardır. Ev reklam çözümünde bunların hiçbiri hesaba katılamaz. Kullanıcıların herhangi bir şekilde etkileşimde olmadığından emin olmak amacıyla, işlevsellik için ihtiyacınız olmasa bile her tür etkileşim etkinliğine (scroll, touch, click) dinleyici eklemeniz gerekir. Böylece kullanıcının etkileşimde bulunmadığından kesinlikle emin olabilirsiniz. Diğer yandan, tarayıcı, karenin sonunda ne kadar zaman kaldığını ve kullanıcının etkileşimde bulunup bulunmadığını tam olarak bildiğinden, requestIdleCallback sayesinde boş zamanlarımızı mümkün olan en verimli şekilde değerlendirmemizi sağlayan bir API elde ederiz.

Biraz daha detaylı inceleyelim ve ondan nasıl yararlanabileceğimizi görelim.

requestIdleCallback kontrol ediliyor

requestIdleCallback henüz yeni olduğu için kullanmaya başlamadan önce kullanıma sunulduğundan emin olun:

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

Ayrıca, setTimeout özelliğine geri dönmeyi gerektiren davranışını da daraltabilirsiniz:

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);
    }

requestIdleCallback, boşta kalma süresini bilmediğinden setTimeout kullanmak çok iyi bir seçenek değildir. Ancak requestIdleCallback kullanılamıyorsa işlevinizi doğrudan çağıracağınız için bu şekilde de çatlamaktan daha kötü olmazsınız. Şim ile requestIdleCallback olduğunda görüşmeleriniz sessizce yönlendirilir. Bu da harika bir şeydir.

Ancak şimdilik bunun var olduğunu varsayalım.

requestIdleCallback'i kullanma

requestIdleCallback çağrısı, ilk parametresi olarak bir geri çağırma işlevi alması açısından requestAnimationFrame işlevine çok benzer:

requestIdleCallback(myNonEssentialWork);

myNonEssentialWork çağrıldığında, işlemeniz için ne kadar süre kaldığını gösteren bir sayı döndüren işlevin bulunduğu bir deadline nesnesi verilir:

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

timeRemaining işlevi, en son değeri elde etmek için çağrılabilir. timeRemaining() sıfır döndürdüğünde daha yapılacak çok işiniz varsa başka bir requestIdleCallback programlayabilirsiniz:

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

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

İşlevinizi garanti etmek

İşler gerçekten yoğun olduğunda ne yaparsınız? Geri aramanızın hiçbir zaman aranmayacağı konusunda endişe duyuyor olabilirsiniz. requestIdleCallback, requestAnimationFrame biçimine benzese de onu isteğe bağlı ikinci bir parametreyle ayırt eder: zaman aşımı özelliğine sahip bir seçenek nesnesi. Bu zaman aşımı, ayarlanırsa tarayıcıya geri çağırma işlemini gerçekleştirmesi için milisaniye cinsinden bir süre verir:

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

Geri çağırma işleminiz zaman aşımının tetiklenmesi nedeniyle yürütülürse iki şey görürsünüz:

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

didTimeout maddesinin doğru olduğunu görürseniz büyük olasılıkla işi yapmak ve bu işle 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);
}

Yaşanan aksaklıklar nedeniyle bu zaman aşımı kullanıcılarınıza (söz konusu iş, uygulamanızın yanıt vermemesine veya kötü çalışmasına neden olabilir) bu parametreyi ayarlarken dikkatli olun. Geri aramayı ne zaman arayacağınıza tarayıcının karar vermesine izin verin.

Analiz verilerini göndermek için requestIdleCallback'i kullanma

Analiz verilerini göndermek için requestIdleCallback API'sinin nasıl kullanıldığına göz atalım. Bu durumda, örneğin gezinme menüsüne dokunmak gibi bir etkinliği izlemek isteriz. Ancak, normalde ekrana gelecekleri animasyonlar nedeniyle bu etkinliği Google Analytics'e hemen göndermekten kaçınmak isteriz. Gönderilecek ve gelecekteki bir zamanda gönderilmesini isteyeceğimiz bir etkinlik dizisi oluşturacağız:

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 kullanmanız gerekecek:

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. Analiz verileri açısından, verilerin yalnızca gelecek bir noktada değil, makul bir zaman diliminde raporlanmasını sağlamak için zaman aşımının kullanılması mantıklıdır.

Son olarak, requestIdleCallback tarafından yürütülecek fonksiyonu 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 olmasaydı analiz verilerinin hemen gönderilmesi gerektiğini düşündüm. Ancak bir üretim uygulamasında, gönderme işleminin herhangi bir etkileşimle çakışmaması ve duraklamaya neden olmaması için zaman aşımı ile gönderme işleminin ertelenmesi daha iyi olabilir.

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

requestIdleCallback ürününün performansa gerçekten yardımcı olabileceği bir başka durum da, sürekli büyüyen, geç yüklenen bir listenin sonuna öğe eklemek gibi gerekli olmayan DOM değişikliklerinin yapılmasıdır. requestIdleCallback öğesinin tipik bir kareye nasıl sığdırıldığına bakalım.

Sıradan bir kare.

Tarayıcı belirli bir karede geri çağırma yapamayacak kadar meşgul olabilir. Bu nedenle, başka bir işlem yapmak için bir karenin sonunda herhangi bir boş zaman olmasını beklememelisiniz. Bu nedenle, setImmediate gibi kare başına çalışır.

Geri çağırma, karenin sonunda tetiklenirse geçerli kare kaydedildikten sonra devam etmek üzere planlanır. Bu, stil değişikliklerinin uygulandığı ve daha da önemlisi, düzenin hesaplanacağı anlamına gelir. Boşta geri çağırma içinde DOM değişiklikleri yaparsak bu düzen hesaplamaları geçersiz kılınır. Sonraki karede herhangi bir düzen okuması varsa (ör. getBoundingClientRect, clientWidth vb.) tarayıcının Zorunlu Eşzamanlı Düzen gerçekleştirmesi gerekir. Bu, potansiyel bir performans sorunu olabilir.

Boşta geri çağırma işleminde DOM değişikliklerinin tetiklenmemesinin bir başka nedeni de, DOM'u değiştirmenin zaman etkisinin tahmin edilememesidir. Böylece, tarayıcının sağladığı son tarihi kolayca aşabiliriz.

En iyi uygulama, DOM değişikliklerinin yalnızca bir requestAnimationFrame geri çağırması içinde yapılmasıdır. Çünkü işlem, tarayıcı tarafından bu tür işler göz önünde bulundurularak planlanmıştır. Bu, kodumuzun bir sonraki requestAnimationFrame geri çağırma işlemine eklenebilecek bir doküman parçası kullanması gerektiği anlamına gelir. Bir VDOM kitaplığı kullanıyorsanız değişiklik yapmak için requestIdleCallback kullanırsınız ancak boşta geri çağırma yerine sonraki requestAnimationFrame geri çağırma işleminde DOM yamalarını uygularsınız.

Bunu da göz önünde bulundurarak koda bakalı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şturuyorum ve doldurmak için textContent özelliğini kullanıyorum. Ancak muhtemelen öğe oluşturma kodunuz işin içinde olur. scheduleVisualUpdateIfNeeded öğesi oluşturulduktan sonra çağrılır. Bu öğe, doküman parçasını gövdeye ekleyecek tek bir requestAnimationFrame geri çağırması 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 olduğu sürece, DOM'ye öğe eklerken artık çok daha az durgunluk göreceğiz. Mükemmel

SSS

  • Çoklu dolgu var mı? Maalesef hayır, ancak setTimeout öğesine şeffaf bir yönlendirme yapmak istiyorsanız bir dolgu vardır. Bu API'nin var olmasının nedeni, web platformunda çok önemli bir boşluk doldurmasıdır. Etkinlik eksikliğini tahmin etmek zordur, ancak çerçevenin sonundaki boş süreyi belirlemek için herhangi bir JavaScript API'sı yoktur; dolayısıyla, en iyi durumda tahminlerde bulunmanız gerekir. setTimeout, setInterval veya setImmediate gibi API'ler iş planlamak için kullanılabilir, ancak requestIdleCallback'daki gibi kullanıcı etkileşimini önleyecek şekilde zamanlanmazlar.
  • Son tarihi aşarsam ne olur? timeRemaining() sıfır döndürür, ancak daha uzun süre koşmayı seçerseniz, tarayıcının çalışmanızı durdurmasından korkmadan bunu yapabilirsiniz. Ancak tarayıcı, kullanıcılarınıza sorunsuz bir deneyim yaşatmak için size bir son tarih verir. Dolayısıyla, çok iyi bir neden olmadığı sürece her zaman son tarihe uymanız gerekir.
  • timeRemaining() işlevinin döndüreceği maksimum değer var mı? Evet, şu anda 50 ms. Duyarlı uygulama sağlamaya çalışırken, kullanıcı etkileşimlerine verilen tüm yanıtlar 100 ms'nin altında tutulmalıdır. Kullanıcı 50 ms'lik pencereyle etkileşimde bulunursa, çoğu durumda boşta geri çağırmanın tamamlanmasına ve tarayıcının kullanıcı etkileşimlerine yanıt vermesine izin vermelidir. Tarayıcı arka arkaya programlanmış birden fazla boşta geri çağırma (geri çağırma) alabilirsiniz (tarayıcı, bunları çalıştırmak için yeterli zaman olduğunu belirlerse).
  • requestIdleCallback'te yapmamam gereken herhangi bir iş var mı? İdeal olarak yaptığınız iş, nispeten tahmin edilebilir özelliklere sahip küçük gruplar (mikro görevler) halinde olmalıdır. Örneğin, özellikle DOM'un değiştirilmesi stil hesaplamalarını, düzeni, boyamayı ve birleştirmeyi tetikleyeceğinden tahmin edilemeyen yürütme sürelerine sahiptir. Dolayısıyla, bir requestAnimationFrame geri çağırmasında yalnızca yukarıda önerildiği şekilde DOM değişiklikleri yapmanız gerekir. Dikkat edilmesi gereken bir başka nokta da Vaat'lerin çözüme ulaştırılması (veya reddedilmesidir). Çünkü geri çağırmalar, boşta olan geri çağırma sona erdikten hemen sonra, hiç zaman kalmasa bile yürütülür.
  • Bir karenin sonunda daima requestIdleCallback alır mıyım? Hayır, her zaman değil. Tarayıcı, bir karenin sonunda veya kullanıcının etkin olmadığı zamanlarda geri çağırmayı programlar. Geri çağırmanın kare başına çağrılmasını beklememelisiniz. Belirli bir zaman aralığında çalıştırılmasını istiyorsanız zaman aşımından yararlanmalısınız.
  • requestIdleCallback ile ilgili birden fazla geri arama gerçekleştirebilir miyim? Evet, aynı anda birden fazla requestAnimationFrame geri araması da yapabilirsiniz. Yine de, ilk geri arama işlemi geri çağırma sırasında kalan süreyi kullanırsa, diğer geri çağırma işlemleri için zaman kalmayacağını unutmamak gerekir. Diğer geri çağırmaların çalıştırılabilmesi için tarayıcı tekrar boşta kalana kadar beklemesi gerekir. Bitirmeye çalıştığınız işe bağlı olarak, boştayken tek bir geri çağırma yaparak işi bu kapsama bölmek daha iyi olabilir. Alternatif olarak, hiçbir geri aramada zaman kaybı yaşanmaması için zaman aşımı süresinden yararlanabilirsiniz.
  • Başka bir öğenin içinde yeni bir aktif olmayan geri çağırma (callback) ayarlarsam ne olur? Yeni boşta kalma geri çağırması, mevcut kare yerine sonraki kareden başlayarak mümkün olan en kısa sürede çalışacak şekilde planlanır.

Boşta kaldı!

requestIdleCallback, kodunuzu kullanıcının engeline takılmadan çalıştırabildiğinizden emin olmanın harika bir yoludur. Kullanımı basittir ve oldukça esnektir. Ancak, henüz yolun başındayız ve teknik özellikler henüz tam olarak belirlenmedi, geri bildirimlerinizi bekliyoruz.

Bu yeni sürümü Chrome Canary'de deneyin, projelerinizde deneyin ve neler yaptığınızı bize bildirin.