requestIdleCallback'i kullanma

Paul Lewis

Birçok site ve uygulamada, yürütülecek çok sayıda komut dosyası bulunur. 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 bu konuda 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, çalışmalarınızı kullanıcıya engellemeden yapma fırsatının olduğu anlamına gelir. Bu özellik Chrome 47'den itibaren kullanılabilir. 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. Evde gösterilen reklamlar bunların hiçbirini hesaba katmaz. 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 kontrol ediliyor

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

requestIdleCallback gibi boşta kalma süresini bilmediğinden setTimeout kullanmak çok iyi bir fikir değildir. Ancak, requestIdleCallback kullanılamıyorsa işlevinizi doğrudan çağıracağınız için bu şekilde devam etmeniz çok daha iyi olmaz. requestIdleCallback kullanılabilir durumdaysa bu dolgu sayesinde aramalarınız sessizce yönlendirilir. Bu da çok iyi bir durumdur.

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

requestIdleCallback'i kullanma

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

requestIdleCallback(myNonEssentialWork);

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

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

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

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

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

İşlevinizin garanti edilmesi

Çok yoğunsanız ne yaparsınız? Geri aranmayacağınız konusunda endişeleriniz olabilir. 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. Bu zaman aşımı ayarlanırsa tarayıcıya geri çağırmayı yürütmesi gereken süreyi milisaniye cinsinden 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 döndürür.
  • deadline nesnesinin didTimeout özelliği doğru 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österildiği 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 öğesinin tipik bir kareye nasıl sığdığına bakalım.

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ışan setImmediate gibi bir değerden farklıdır.

Geri çağırma, çerçevenin sonunda tetiklenirse mevcut kare kaydedildikten sonra gerçekleşecek şekilde planlanır. Bu, stil değişikliklerinin uygulandığı ve daha da önemlisi, düzenin hesaplanacağı 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 yalnızca bir requestAnimationFrame geri çağırma işlevi içinde DOM değişiklikleri 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şturur ve doldurmak için textContent özelliğini kullanırım, ancak muhtemelen öğe oluşturma kodunuzda daha fazla sorun olur. Öğ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

  • Çoklu dolgu 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ının çalışmanızı durdurması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ürebileceği maksimum bir 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 planlanan birden fazla boşta geri çağırma alabilirsiniz (tarayıcı, bunları çalıştırmak için yeterli zaman olduğunu belirlerse).
  • requestIdleCallback üzerinde yapmam gereken herhangi bir çalışma var mı? İdeal olarak, yaptığınız işi nispeten tahmin edilebilir özelliklere sahip küçük parçalar (mikro görevler) yapmalısınız. Örneğin, stil hesaplamalarını, düzeni, boyamayı ve kompozisyon oluşturmayı tetikleyeceğinden özellikle DOM'un değiştirilmesi, tahmin edilemeyen yürütme sürelerine 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 kare başına ç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 arama işleminiz, geri çağırma sırasında kalan süreyi kullanırsa diğer geri çağırmalar için başka zaman kalmayacağını hatırlatmak isteriz. 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 mükemmel 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.