Size "ana ileti dizisini engelleme" söylendi "uzun görevlerinizi bölün", Peki bunları yapmak ne anlama geliyor?
JavaScript uygulamalarını hızlı tutmaya yönelik genel tavsiyeler, genellikle aşağıdaki tavsiyeleri içerir:
- "Ana ileti dizisini engelleme."
- "Uzun görevlerinizi bölün."
Bu çok güzel bir tavsiye ama ne gibi bir çalışma yapılır? Daha az JavaScript göndermek iyi bir yöntemdir. Peki bu otomatik olarak daha duyarlı kullanıcı arayüzlerine denk midir? Olabilir ama belki de değil.
JavaScript'te görevlerin nasıl optimize edileceğini anlamak için öncelikle görevlerin ne olduğunu ve tarayıcının bunları nasıl işlediğini bilmeniz gerekir.
Görev nedir?
Görev, tarayıcının yaptığı her ayrı ayrı iştir. Bu çalışmalar arasında HTML ve CSS'nin oluşturulması, ayrıştırılması, JavaScript'in çalıştırılması ve doğrudan kontrol edemediğiniz diğer çalışma türleri bulunur. Bunların arasında, yazdığınız JavaScript belki de en büyük görev kaynağıdır.
JavaScript ile ilişkili görevler, performansı birkaç şekilde etkiler:
- Tarayıcı, başlatma sırasında bir JavaScript dosyası indirdiğinde, daha sonra yürütülebilmesi için JavaScript'i ayrıştırıp derlemek üzere görevleri sıraya alır.
- Sayfanın ömrü içindeki bazı zamanlarda ise; etkinlik işleyiciler, JavaScript'e dayalı animasyonlar ve analiz toplama gibi arka plan etkinlikleri üzerinden etkileşimlerin yönlendirilmesi gibi, JavaScript çalışırken görevler sıraya alınır.
Web çalışanları ve benzer API'ler dışında, tüm bu şeyler ana iş parçacığında gerçekleşir.
Ana ileti dizisi nedir?
Ana iş parçacığı, çoğu görevin tarayıcıda çalıştırıldığı ve yazdığınız neredeyse tüm JavaScript'in yürütüldüğü yerdir.
Ana iş parçacığı tek seferde yalnızca bir görev işleyebilir. 50 milisaniyeden uzun süren görevler uzun bir görevdir. 50 milisaniyeyi aşan görevlerde, görevin toplam süresi eksi 50 milisaniye ise görevin engelleme süresi olarak bilinir.
Tarayıcı, herhangi bir uzunluktaki görev devam ederken etkileşimlerin gerçekleşmesini engeller. Ancak görevler çok uzun süre devam ettiği sürece kullanıcı bunu algılayamaz. Bununla birlikte, bir kullanıcı çok sayıda uzun görev varken bir sayfayla etkileşimde bulunmaya çalıştığında ana iş parçacığı çok uzun süre engellenmiş olsa bile kullanıcı arayüzü yanıt vermiyor, hatta bozulmuş gibi görünebilir.
Ana iş parçacığının çok uzun süre engellenmesini önlemek için uzun bir görevi birkaç küçük göreve bölebilirsiniz.
Bu önemlidir çünkü görevler bölündüğünde tarayıcı, kullanıcı etkileşimleri de dahil olmak üzere daha yüksek öncelikli işlere çok daha erken yanıt verebilir. Sonrasında kalan görevler sonuna kadar yürütülür ve ilk sıraya aldığınız işin tamamlandığından emin olunur.
Önceki resmin üst kısmında, kullanıcı etkileşimi tarafından sıraya alınan bir etkinlik işleyicinin, başlamadan önce uzun bir görevi beklemesi gerekiyordu. Bu da etkileşimin gerçekleşmesini geciktirmektedir. Bu senaryoda, kullanıcı gecikmeyi fark etmiş olabilir. Alt tarafta, etkinlik işleyici daha erken çalışmaya başlayabilir ve etkileşim anında hissetmiş olabilir.
Artık görevleri bölmenin neden önemli olduğunu bildiğinize göre, bunu JavaScript'te nasıl yapacağınızı öğrenebilirsiniz.
Görev yönetimi stratejileri
Yazılım mimarisinde yaygın olarak yaptığımız bir tavsiye, çalışmanızı daha küçük işlevlere bölmenizdir:
function saveSettings () {
validateForm();
showSpinner();
saveToDatabase();
updateUI();
sendAnalytics();
}
Bu örnekte; formu doğrulamak, döner simge göstermek, uygulama arka ucuna veri göndermek, kullanıcı arayüzünü güncellemek ve analizler göndermek için beş işlev çağıran saveSettings()
adlı bir işlev bulunmaktadır.
saveSettings()
, kavramsal olarak iyi tasarlanmış. Bu işlevlerden birinde hata ayıklamanız gerekirse her bir işlevin ne işe yaradığını öğrenmek için proje ağacını tarayabilirsiniz. İşleri bu şekilde ayırmak, projelerde gezinmeyi ve bakımını kolaylaştırır.
Ancak buradaki olası bir sorun, JavaScript'in bu işlevlerin her birini saveSettings()
işlevi içinde yürütüldüğü için ayrı görevler olarak çalıştırmamasıdır. Bu, beş işlevin tümünün tek bir görev olarak çalışacağı anlamına gelir.
En iyi senaryoda, bu işlevlerden yalnızca biri bile görevin toplam uzunluğuna 50 milisaniye veya daha fazla katkı sağlayabilir. En kötü durumda, bu görevlerin çoğu, özellikle de kaynak açısından kısıtlı cihazlarda çok daha uzun süre çalışabilir.
Kod yürütülmesini manuel olarak ertele
setTimeout()
, geliştiricilerin görevleri daha küçük görevlere ayırmak için kullandıkları yöntemlerden biridir. Bu teknikle, işlevi setTimeout()
ürününe iletirsiniz. Bu işlem, 0
zaman aşımı belirtseniz bile geri çağırmanın yürütülmesini ayrı bir göreve erteler.
function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Defer work that isn't user-visible to a separate task:
setTimeout(() => {
saveToDatabase();
sendAnalytics();
}, 0);
}
Bu, getiri olarak bilinir ve en iyi şekilde, sıralı olarak çalışması gereken bir dizi işlev için işe yarar.
Ancak kodunuz her zaman bu şekilde düzenlenmeyebilir. Örneğin, bir döngüde işlenmesi gereken büyük miktarda veriniz olabilir ve çok sayıda iterasyon varsa bu görev çok uzun sürebilir.
function processData () {
for (const item of largeDataArray) {
// Process the individual item here.
}
}
Burada setTimeout()
kullanılması, geliştiricinin ergonomisi nedeniyle sorunludur ve her iterasyon hızlı bir şekilde gerçekleşse bile tüm veri dizisinin işlenmesi çok uzun sürebilir. Her şeyin bir arada olması gerekir. setTimeout()
ise bu iş için doğru araç değil. En azından bu şekilde kullanıldığında gayet iyi.
Getiri puanları oluşturmak için async
/await
değerini kullanın
Kullanıcılara yönelik önemli görevlerin düşük öncelikli görevlerden önce gerçekleştirilmesini sağlamak için Görev sırasını kısa süreliğine keserek, ana iş parçacığına getiri elde edebilirsiniz. daha önemli görevler için tarayıcı fırsatlarından yararlanabilirsiniz.
Daha önce açıklandığı gibi, ana ileti dizisine veri vermek için setTimeout
kullanılabilir. Bununla birlikte, kolaylık ve daha iyi okunabilirlik için Promise
içinde setTimeout
öğesini çağırabilir ve geri çağırma olarak resolve
yöntemini iletebilirsiniz.
function yieldToMain () {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
.
yieldToMain()
işlevinin avantajı, herhangi bir async
işlevinde await
işlevi gerçekleştirebilmenizdir. Önceki örneği temel alarak, çalıştırılacak bir işlev dizisi oluşturabilir ve her çalıştırıldıktan sonra ana iş parçacığına getiri yapabilirsiniz:
async function saveSettings () {
// Create an array of functions to run:
const tasks = [
validateForm,
showSpinner,
saveToDatabase,
updateUI,
sendAnalytics
]
// Loop over the tasks:
while (tasks.length > 0) {
// Shift the first task off the tasks array:
const task = tasks.shift();
// Run the task:
task();
// Yield to the main thread:
await yieldToMain();
}
}
Sonuçta, eskiden monolitik bir görev artık ayrı görevlere bölünmüştür.
'nı inceleyin.Özel bir planlayıcı API'si
setTimeout
, görevleri bölmek için etkili bir yoldur ancak dezavantajı olabilir: Kodu sonraki bir görevde çalışacak şekilde erteleyerek ana iş parçacığına veri koyduğunuzda bu görev, sıranın sonuna eklenir.
Sayfanızdaki tüm kodu kontrol ediyorsanız görevleri öncelik sırasına koyma özelliğiyle kendi planlayıcınızı oluşturabilirsiniz ancak üçüncü taraf komut dosyaları planlayıcınızı kullanmaz. Aslında bu tür ortamlarda çalışmaya öncelik veremezsiniz. Verileri yalnızca parçalara ayırabilir veya açık bir şekilde kullanıcı etkileşimlerini sağlayabilirsiniz.
Scheduler API, görevlerin daha ayrıntılı bir şekilde planlanmasına olanak tanıyan postTask()
işlevini sunar. Ayrıca, düşük öncelikli görevlerin ana iş parçacığına üretilmesi için tarayıcının işi önceliklendirmesine yardımcı olmanın bir yoludur. postTask()
, taahhütleri kullanır ve üç priority
ayarından birini kabul eder:
- En düşük öncelikli görevler için
'background'
. - Orta öncelikli görevler için
'user-visible'
.priority
ayarlanmazsa varsayılan olarak bu ayar kullanılır. - Yüksek öncelikte çalışması gereken kritik görevler için
'user-blocking'
.
postTask()
API'nin üç görevi mümkün olan en yüksek öncelikte, kalan iki görevi ise mümkün olan en düşük öncelikte çalıştırmak için kullanıldığı aşağıdaki kodu örnek olarak ele alalım.
function saveSettings () {
// Validate the form at high priority
scheduler.postTask(validateForm, {priority: 'user-blocking'});
// Show the spinner at high priority:
scheduler.postTask(showSpinner, {priority: 'user-blocking'});
// Update the database in the background:
scheduler.postTask(saveToDatabase, {priority: 'background'});
// Update the user interface at high priority:
scheduler.postTask(updateUI, {priority: 'user-blocking'});
// Send analytics data in the background:
scheduler.postTask(sendAnalytics, {priority: 'background'});
};
Burada görevlerin önceliği, kullanıcı etkileşimleri gibi tarayıcıya öncelik verilen görevlerin arayla verimli şekilde yerine getirilebileceği şekilde planlanır.
Bu, postTask()
özelliğinin nasıl kullanılabildiğine dair basit bir örnektir. Gerektiğinde farklı TaskController
örnekleri için öncelikleri değiştirme olanağı da dahil olmak üzere, görevler arasında öncelikleri paylaşabilen farklı TaskController
nesneleri örneklenebilir.
Yakında kullanıma sunulacak scheduler.yield()
API ile sürekliliği olan yerleşik getiri
Scheduler API'sına önerilen bir ekleme de scheduler.yield()
, tarayıcıdaki ana iş parçacığına verim sağlamak üzere özel olarak tasarlanmış bir API'dir. Bu işlevin kullanımı, bu kılavuzun önceki bölümlerinde gösterilen yieldToMain()
işlevine benzer:
async function saveSettings () {
// Create an array of functions to run:
const tasks = [
validateForm,
showSpinner,
saveToDatabase,
updateUI,
sendAnalytics
]
// Loop over the tasks:
while (tasks.length > 0) {
// Shift the first task off the tasks array:
const task = tasks.shift();
// Run the task:
task();
// Yield to the main thread with the scheduler
// API's own yielding mechanism:
await scheduler.yield();
}
}
Bu kod çoğunlukla tanıdık gelecektir, ancak yieldToMain()
yerine
await scheduler.yield()
.
scheduler.yield()
ürününün faydası devamlılıktır. Diğer bir deyişle, bir görev kümesinin ortasında verim alırsanız diğer planlanmış görevler getiri noktasından sonra da aynı sırayla devam eder. Bu sayede, üçüncü taraf komut dosyalarından gelen kodların, kodunuzun yürütülme sırasını kesintiye uğratması önlenir.
user-blocking
önceliğinin yüksek olması nedeniyle scheduler.postTask()
ürününün priority: 'user-blocking'
ile birlikte kullanılması da yüksek ihtimalle devam edeceği için bu süre zarfında alternatif olarak bu yaklaşım kullanılabilir.
setTimeout()
(veya scheduler.postTask()
priority: 'user-visibile'
ile veya açıkça belirtilmezse priority
) kullanıldığında görev, sıranın arkasına planlanır ve beklemedeki diğer görevler devam etmeden önce çalıştırılabilir.
isInputPending()
adını kullanma
Tarayıcı Desteği
- 87
- 87
- x
- x
isInputPending()
API, kullanıcının bir sayfayla etkileşim kurmayı deneyip denemediğini ve yalnızca giriş beklemedeyse getiriyi kontrol etmenin bir yolunu sağlar.
Bu sayede, bekleyen giriş yoksa JavaScript'in devam etmesi ve sonuç olarak görev sırasının sonuna gelmesi yerine işleme devam etmesi sağlanır. Bu, Gönderme Amacı'nda ayrıntılı olarak açıklandığı gibi, aksi halde ana iş parçacığına dönüşmeyebilecek siteler için etkileyici performans iyileştirmeleri sağlayabilir.
Ancak bu API'nin kullanıma sunulmasından bu yana, özellikle INP'nin kullanıma sunulmasıyla birlikte getiri anlayışımız arttı. Artık bu API'yi kullanmanızı önermeyiz. Bunun yerine, çeşitli nedenlerden dolayı girişin beklemede olup olmamasından bağımsız olarak sonuç elde etmenizi öneririz:
isInputPending()
, kullanıcı bazı durumlarda etkileşimde bulunmasına rağmen yanlışlıklafalse
sonucu döndürebilir.- Görevlerin gerçekleşmesi gereken tek durum girdi değildir. Animasyonlar ve diğer düzenli kullanıcı arayüzü güncellemeleri, duyarlı bir web sayfası sağlamak kadar aynı derecede önemli olabilir.
- O zamandan beri,
scheduler.postTask()
vescheduler.yield()
gibi önemli endişeleri gidermek için daha kapsamlı getiri API'leri kullanıma sunuldu.
Sonuç
Görevleri yönetmek zor bir iştir, ancak böyle yapmak sayfanızın kullanıcı etkileşimlerine daha hızlı yanıt vermesini sağlar. Görevleri yönetmek ve önceliklendirmek için tek bir öneri yok, birkaç farklı teknik var. Tekrar hatırlatmak gerekirse, görevleri yönetirken göz önünde bulundurmanız gereken başlıca noktalar şunlardır:
- Kullanıcılara yönelik kritik görevler için ana ileti dizisine odaklanın.
postTask()
ile görevlere öncelik verin.scheduler.yield()
ile denemeler yapabilirsiniz.- Son olarak, fonksiyonlarınızda mümkün olduğunca az çalışın.
Bu araçlardan bir veya daha fazlasını kullanarak uygulamanızdaki işi, kullanıcının ihtiyaçlarına öncelik verecek ve kritik öneme sahip olmayan işlerin yapılmasını sağlayacak şekilde yapılandırabilmeniz gerekir. Bu sayede daha duyarlı ve kullanımı daha keyifli olan daha iyi bir kullanıcı deneyimi oluşturabilirsiniz.
Bu rehberi teknik olarak incelediği için Philip Walton'a özel teşekkürlerimizi sunuyoruz.
Küçük resim, Amirali Mirhashemian'ın izniyle Unsplash'ten alınmıştır.