Birçok site ve uygulamada, yürütülecek çok sayıda komut dosyası bulunur. JavaScript'inizin genellikle mümkün olan en kısa sürede çalıştırılması gerekir, ancak aynı zamanda kullanıcının da çalışmasını da istemezsiniz. Analiz verilerini, kullanıcı sayfayı kaydırırken gönderirseniz veya düğmelere dokunurken DOM'ye öğeler eklerseniz web uygulamanız yanıt vermeyebilir ve bu da kötü bir kullanıcı deneyimine neden olabilir.
Neyse ki artık bu konuda size yardımcı olabilecek bir API var: requestIdleCallback
. requestAnimationFrame
uygulamasını kullanmak, 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 çalışmayı planlar. Bu, çalışmalarınızı kullanıcıya engellemeden yapma fırsatının olduğu anlamına gelir. Chrome 47 sürümünden itibaren kullanılabilir, böylece Chrome Canary'yi kullanarak hemen deneyebilirsiniz! Bu, deneysel bir özelliktir ve spesifikasyon değişmeye devam ettiği için gelecekte bazı şeyler değişebilir.
requestIdleCallback'i neden kullanmalıyım?
Zorunlu olmayan işleri kendi başınıza planlamak oldukça zordur. requestAnimationFrame
geri çağırması yürütüldükten sonra stil hesaplamaları, düzen, boyama ve çalıştırılması gereken diğer tarayıcı dahili öğeleri olduğu için tam olarak ne kadar kare süresinin kaldığını öğrenmek mümkün değildir. Evde gösterilen reklamlar bunların hiçbirini hesaba katmaz. Kullanıcının herhangi bir şekilde etkileşimde olmadığından emin olmak için, işlevsellik için ihtiyaç duymasanız bile her tür etkileşim etkinliğine (scroll
, touch
, click
) işleyiciler eklemeniz gerekir. Böylece kullanıcının etkileşim kurmadığından kesinlikle emin olabilirsiniz. Öte yandan tarayıcı, karenin sonunda tam olarak ne kadar süre kaldığını ve kullanıcının etkileşime geçip geçmediğini bilir. Bu nedenle requestIdleCallback
aracılığıyla boş zamanı mümkün olan en verimli şekilde değerlendirmemizi sağlayan bir API elde ederiz.
Şimdi, biraz daha ayrıntılı bir şekilde inceleyelim ve bundan nasıl yararlanabileceğimizi görelim.
requestIdleCallback kontrol ediliyor
requestIdleCallback
daha yolun başında olduğu için kullanmaya başlamadan önce kullanılabilir olup olmadığını kontrol etmelisiniz:
if ('requestIdleCallback' in window) {
// Use requestIdleCallback to schedule work.
} else {
// Do what you’d do today.
}
Ayrıca, davranışını değiştirerek de setTimeout
işlevini geri alabilirsiniz:
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
için dolgu kullanıldığında, sesli aramalarınız sessizce yönlendirilir ve bu iyi bir gelişmedir.
Ama şimdilik bunun var olduğunu varsayalım.
requestIdleCallback Kullanma
requestIdleCallback
çağrısı, ilk parametresi olarak bir geri çağırma işlevi alması açısından requestAnimationFrame
ile çok benzerdir:
requestIdleCallback(myNonEssentialWork);
myNonEssentialWork
çağrıldığında, kendisine bir deadline
nesnesi verilir. Bu nesne, çalışmanız için ne kadar zaman 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
İşler gerçekten çok yoğunsa ne yaparsınız? Geri aramanızın hiçbir zaman aranmayabileceği konusunda endişeleniyor olabilirsiniz. requestIdleCallback
, requestAnimationFrame
özelliğine benzese de isteğe bağlı ikinci bir parametre, yani zaman aşımı özelliğine sahip seçenekler nesnesi olması bakımından farklıdır. 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ı 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 çalışmayı yürütüp süreci tamamlamak isteyeceksinizdir:
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ının kullanıcılarınıza neden olabileceği olası kesintiler nedeniyle (bu işlem uygulamanızın yanıt vermemesine veya titremesine neden olabilir) bu parametreyi ayarlarken dikkatli olun. Mümkünse tarayıcının, geri çağırma işlemini ne zaman yapacağına karar vermesine izin verin.
Analiz verilerini göndermek için requestIdleCallback kullanma
Analiz verilerini göndermek için requestIdleCallback
hizmetinin nasıl kullanıldığına göz atalım. Bu durumda, muhtemelen gezinme menüsüne dokunma gibi bir etkinliği izlemek isteriz. Ancak, normalde ekranda canlandırıldığı için bu etkinliğin Google Analytics'e hemen gönderilmesini istemeyiz. Gönderilecek ve ileride 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 beklemedeki etkinlikleri işlemek için requestIdleCallback
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ı belirlediğimi görebilirsiniz, ancak bu değer uygulamanıza bağlı olacaktır. Analiz verileri söz konusu olduğunda, verilerin yalnızca gelecekteki bir zaman yerine makul bir zaman aralığında raporlandığından emin olmak için bir zaman aşımının kullanılması mantıklıdır.
Son olarak, requestIdleCallback
tarafından çalıştırılacak 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, herhangi bir etkileşimle çakışmaması ve soruna neden olmaması için gönderme zamanını zaman aşımıyla geciktirmek muhtemelen daha iyi olacaktır.
DOM değişiklikleri yapmak için requestIdleCallback 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ığına bakalım.
Tarayıcı belirli bir çerçevede geri çağırma yapamayacak kadar meşgul olabilir. Bu nedenle, bir karenin sonunda başka iş yapmak için hiç boş bir zaman olması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şta geri çağırmanın içinde DOM değişiklikleri yaparsak bu düzen hesaplamaları geçersiz hale gelir. Bir sonraki karede herhangi bir düzen okuması varsa (ör. getBoundingClientRect
, clientWidth
vb. için tarayıcının Zorunlu Senkronize Düzen'i gerçekleştirmesi gerekir. Bu durum, performans sorunu yaşanmasına yol açabilir.
Boşta geri çağırma işlevinde DOM değişikliklerini tetiklememenin bir diğer nedeni de, DOM'yi değiştirmenin zaman üzerindeki etkisinin öngörülememesidir. 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 sonraki requestAnimationFrame
geri çağırmasına eklenebilecek bir doküman parçası kullanması gerektiği anlamına gelir. VDOM kitaplığı kullanıyorsanız değişiklik yapmak için requestIdleCallback
uzantısını kullanırsınız. Ancak DOM yamalarını, sonraki requestAnimationFrame
geri çağırmasında (boşta geri çağırmada değil) uygulamanız gerekir.
Bunu göz önünde bulundurarak 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şturuyor ve doldurmak için textContent
özelliğini kullanıyorum ancak muhtemelen öğe oluşturma kodunuz daha fazla işin içine girecektir. scheduleVisualUpdateIfNeeded
öğesi oluşturulduktan sonra çağrılır. Bu işlem, 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 yolundaysa artık DOM'ye öğe eklerken çok daha az sorun olacağını göreceğiz. Mükemmel!
SSS
- Çoklu dolgu var mı?
Maalesef değil, ancak
setTimeout
öğesine şeffaf bir yönlendirme yapmak istiyorsanız bir dolgu vardır. Bu API'nin var olmasının nedeni, web platformunda büyük bir boşluğu kapatmasıdır. Etkinlik eksikliğinin olduğunu tahmin etmek zordur, ancak çerçevenin sonundaki boş süreyi belirleyecek JavaScript API'si yoktur, bu yüzden en iyi tahminlerde bulunmanız gerekir.setTimeout
,setInterval
veyasetImmediate
gibi API'ler işi planlamak için kullanılabilir, ancak bunlar,requestIdleCallback
gibi kullanıcı etkileşimini önlemek için zaman aşımına uğramaz. - Son tarihi aşarsam ne olur?
timeRemaining()
değeri sıfır döndürürse ancak daha uzun süre çalışmayı seçerseniz tarayıcının çalışmayı durdurmasından korkmadan bunu yapabilirsiniz. Ancak tarayıcı, kullanıcılarınıza sorunsuz bir deneyim sunmanız için size bir son tarih verir. Dolayısıyla, çok iyi bir neden olmadığı sürece her zaman son tarihe uymalısınız. timeRemaining()
öğesinin döndüreceği maksimum değer var mı? Evet, şu anda 50 ms. Duyarlı uygulama sunmaya devam ederken kullanıcı etkileşimlerine verilen tüm yanıtlar 100 ms'nin altında tutulmalıdır. Kullanıcı 50 ms. penceresiyle etkileşime geçerse ç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. 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, özellikle DOM'nin değiştirilmesi stil hesaplamalarını, düzeni, boyama ve birleştirmeyi tetikleyeceğinden öngörülemeyen yürütme süreleri olacaktır. Bu nedenle, yukarıda önerildiği gibi yalnızca bir
requestAnimationFrame
geri çağırmasında DOM değişiklikleri yapmanız gerekir. Dikkatli olunması gereken bir diğer nokta da vaatlerin çözüme kavuşturulması (veya reddedilmesidir.) çünkü geri çağırmalar, geri arama süresi kalmasa bile boşta kalma geri çağırması bittikten hemen sonra yürütülür. - Bir karenin sonunda her zaman
requestIdleCallback
olacak mı? Hayır, her zaman değil. Tarayıcı, bir karenin sonunda boş zaman olduğunda veya kullanıcının etkin olmadığı dönemlerde geri çağırmayı planlar. Geri çağırmanın kare başına çağrılmasını beklememeniz gerekir. Geri çağırmanın belirli bir zaman aralığında çalıştırılmasını istiyorsanız zaman aşımından faydalanmanız gerekir. - Birden fazla
requestIdleCallback
geri aramam olabilir mi? Evet, dilerseniz birden fazlarequestAnimationFrame
geri araması yapabilirsiniz. Ancak ilk geri arama işleminiz, geri arama sırasında kalan süreyi kullanırsa diğer geri çağırma işlemleri için başka zaman kalmayacağını hatırlatmak isteriz. Bu durumda diğer geri çağırma işlemlerinin çalıştırılabilmesi için tarayıcının bir sonraki boşta olana kadar beklemesi gerekir. Yapmaya çalıştığınız işe bağlı olarak, boşta bir geri çağırma olması ve işi oraya bölmesi daha iyi olabilir. Alternatif olarak, geri çağırmaların uzun süre kalmaması için zaman aşımından yararlanabilirsiniz. - Başka bir anahtarın içinde yeni bir boşta geri çağırma yaparsam ne olur? Yeni boşta geri çağırma, sonraki kareden (geçerli kare yerine) başlayarak mümkün olan en kısa sürede çalışacak şekilde planlanır.
Boşta kalma açık!
requestIdleCallback
, kullanıcınıza engel olmadan kodunuzu çalıştırabilmenizi sağlamanın harika bir yoludur. Kullanımı kolaydır ve çok esnektir. Henüz çok erken ve spesifikasyonlar henüz netleşmedi. Bu nedenle geri bildirimlerinizi bekliyoruz.
Chrome Canary'de göz atın, projelerinizde değişiklikler yapın ve işlerin nasıl gittiğini bize bildirin!