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.
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
nesnesinindidTimeout
ö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.
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
veyasetImmediate
gibi API'ler iş planlamak için kullanılabilir, ancakrequestIdleCallback
'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 fazlarequestAnimationFrame
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.