JavaScript uygulamalarınızı daha hızlı hale getirmek için genellikle "Ana iş parçacığını engellemeyin" ve "Uzun görevlerinizi bölün" tavsiyeleri yer alır. Bu sayfada, bu tavsiyenin ne anlama geldiği ve JavaScript'te görevleri optimize etmenin neden önemli olduğu açıklanmaktadır.
Görev nedir?
Görev, tarayıcının yaptığı ayrı bir iştir. Bunlar arasında oluşturma, HTML ve CSS'yi ayrıştırma, yazdığınız JavaScript kodunu çalıştırma ve doğrudan kontrol sahibi olmadığınız diğer işlemler yer alır. Sayfalarınızın JavaScript'i, tarayıcı görevlerinin başlıca kaynaklarından biridir.
Görevler, performansı çeşitli şekillerde etkiler. Örneğin, bir tarayıcı başlatma sırasında JavaScript dosyası indirdiğinde, yürütülebilmesi için JavaScript'i ayrıştırıp derlemek üzere görevleri sıraya alır. Sayfa yaşam döngüsünün ilerleyen aşamalarında, etkinlik işleyiciler üzerinden etkileşimleri, JavaScript odaklı animasyonları ve analiz toplama gibi arka plan etkinliklerini yönlendirme gibi diğer görevler, JavaScript'iniz çalıştığında başlar. Web çalışanları ve benzer API'ler dışında tüm bunlar 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'lerin yürütüldüğü yerdir.
Ana iş parçacığı bir kerede yalnızca bir görev işleyebilir. 50 milisaniyeden uzun süren görevler uzun bir görev olarak kabul edilir. Kullanıcı, uzun bir görev veya oluşturma güncellemesi sırasında sayfayla etkileşimde bulunmaya çalışırsa tarayıcının bu etkileşimi gerçekleştirmek için beklemesi gerekir. Bu da gecikmeye neden olur.
Bunu önlemek için her uzun görevi her biri daha az zaman alan daha küçük görevlere bölün. Buna uzun görevleri bölme denir.
Görevleri ayırmak, tarayıcının diğer görevler arasında kullanıcı etkileşimleri de dahil olmak üzere daha yüksek öncelikli çalışmalara yanıt vermesi için daha fazla fırsat sunar. Bu, etkileşimlerin çok daha hızlı gerçekleşmesini sağlar. Aksi takdirde, tarayıcı uzun bir görevin tamamlanmasını beklerken kullanıcı gecikme yaşayabilir.
Görev yönetimi stratejileri
JavaScript, görev yürütmenin çalıştırmaya çalıştır modelini kullandığından her işlevi tek bir görev olarak değerlendirir. Diğer bir deyişle, aşağıdaki örnek gibi başka birden çok işlev çağıran bir işlevin, çağrılan tüm işlevler tamamlanana kadar çalışması gerekir. Bu da tarayıcıyı yavaşlatır:
function saveSettings () { //This is a long task.
validateForm();
showSpinner();
saveToDatabase();
updateUI();
sendAnalytics();
}
Kodunuz birden fazla yöntem çağıran işlevler içeriyorsa kodu birden fazla işleve bölün. Bu hem tarayıcıya etkileşime yanıt vermesi için daha fazla fırsat sağlar hem de kodunuzun okunmasını, korunmasını ve test edilmesini kolaylaştırır. Aşağıdaki bölümlerde, uzun işlevleri ayırmak ve bunları oluşturan görevlerin önceliğini belirlemek için yararlanabileceğiniz bazı stratejiler adım adım açıklanmıştır.
Kod yürütmeyi manuel olarak ertele
İlgili işlevi setTimeout()
işlevine ileterek bazı görevlerin yürütülmesini erteleyebilirsiniz. Bu, 0
zaman aşımı belirtseniz bile işe yarar.
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 en çok, sırayla çalışması gereken bir dizi işlev için işe yarar. Farklı şekilde düzenlenen kod için farklı bir yaklaşım gerekir. Bir sonraki örnek, döngü kullanarak büyük miktarda veriyi işleyen bir işlevdir. Veri kümesi ne kadar büyük olursa bu işlem o kadar uzun sürer ve döngüde setTimeout()
yerleştirmek için iyi bir yer olmayabilir:
function processData () {
for (const item of largeDataArray) {
// Process the individual item here.
}
}
Neyse ki kod yürütmeyi sonraki bir göreve ertelemenizi sağlayan birkaç API daha vardır. Daha hızlı zaman aşımları için postMessage()
kullanmanızı öneririz.
İşleri requestIdleCallback()
kullanarak da bölebilirsiniz ancak görevleri en düşük öncelikli olarak ve yalnızca tarayıcının boşta kaldığı süre boyunca planlar. Diğer bir deyişle, ana iş parçacığı özellikle yoğunsa requestIdleCallback()
ile planlanan görevler hiçbir zaman çalıştırılamayabilir.
Getiri puanı oluşturmak için async
/await
kullanın
Kullanıcılara yönelik önemli görevlerin daha düşük öncelikli görevlerden önce gerçekleştirildiğinden emin olmak için tarayıcıya daha önemli görevleri çalıştırma fırsatı vermek amacıyla görev sırasını kısa bir süre keserek ana iş parçacığı elde edin.
Bunu yapmanın en basit yolu, setTimeout()
çağrısıyla çözümlenen bir Promise
kullanmaktır:
function yieldToMain () {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
saveSettings()
işlevinde, her işlev çağrısından sonra yieldToMain()
işlevini await
yaparsanız her adımdan sonra ana iş parçacığı elde edebilirsiniz. Böylece uzun göreviniz birden çok göreve etkili şekilde bölünür:
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();
}
}
Önemli nokta: Her işlev çağrısından sonra veri sağlamanız gerekmez. Örneğin, kullanıcı arayüzünde kritik güncellemelerle sonuçlanan iki işlev çalıştırıyorsanız muhtemelen ikisi arasında geçiş yapmak istemezsiniz. Mümkünse önce bu çalışmayı bırakın, ardından kullanıcının görmediği, arka planda veya daha az kritik düzeyde iş yapan işlevler arasında geçiş yapmayı düşünün.
Özel planlayıcı API'sı
Şimdiye kadar bahsedilen API'ler görevleri bölmenize yardımcı olabilir ancak bu API'lerin ciddi bir dezavantajı vardır. Kodu daha sonraki bir görevde çalışacak şekilde erteleyerek ana iş parçacığına giderseniz bu kod görev sırasının sonuna eklenir.
Sayfanızdaki tüm kodları kontrol ediyorsanız görevlere öncelik vermek için kendi planlayıcınızı oluşturabilirsiniz. Ancak üçüncü taraf komut dosyaları planlayıcınızı kullanmaz. Bu nedenle, bu durumda çalışmaya öncelik veremezsiniz. Bunu yalnızca bölebilir veya kullanıcı etkileşimlerine sağlayabilirsiniz.
Planlayıcı API'si, görevlerin daha ayrıntılı bir şekilde planlanmasına olanak tanıyan postTask()
işlevini sunar. Bu işlev, düşük öncelikli görevlerin ana iş parçacığına dönüşmesi için tarayıcının çalışmalara öncelik vermesine yardımcı olabilir. postTask()
, vaatleri kullanır ve bir priority
ayarını kabul eder.
postTask()
API'nin üç önceliği vardır:
- En düşük öncelikli görevler için
'background'
. - Orta düzeyde öncelikli görevler için
'user-visible'
.priority
ayarlanmamışsa varsayılan olarak bu ayar kullanılır. - Yüksek öncelikli çalışması gereken kritik görevler için
'user-blocking'
.
Aşağıdaki örnek kod, üç görevi mümkün olan en yüksek öncelikte, geri kalan iki görevi ise mümkün olan en düşük öncelikte çalıştırmak için postTask()
API'yi kullanır:
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 planlanır. Böylece, kullanıcı etkileşimleri gibi tarayıcı öncelikli görevler işe yarayabilir.
Gerektiğinde farklı TaskController
örneklerinin önceliklerini değiştirebilme dahil, görevler arasında öncelikleri paylaşan farklı TaskController
nesneleri de örneklendirmeniz mümkündür.
Yakında kullanıma sunulacak scheduler.yield()
API ile devamlı yerleşik getiri
Önemli nokta: scheduler.yield()
ile ilgili daha ayrıntılı bir açıklama için kaynak denemesi (sonucundan beri) ve açıklayıcı hakkında bilgi edinin.
Planlayıcı API'sine önerilen bir ekleme işlemi de tarayıcıdaki ana iş parçacığını oluşturmak için özel olarak tasarlanmış bir API'dir scheduler.yield()
. Kullanımı, bu sayfada daha önce 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 büyük ölçüde tanıdık gelir, ancak yieldToMain()
yerine await scheduler.yield()
kodunu kullanır.
scheduler.yield()
özelliğinin avantajı devamlı olmasıdır. Diğer bir deyişle, bir görev grubunun ortasında getiri elde ederseniz planlanan diğer görevler, getiri noktasından sonra aynı sırada devam eder. Bu, üçüncü taraf komut dosyalarının, kodunuzun çalıştığı sırayı kontrol etmesini önler.
priority: 'user-blocking'
ile scheduler.postTask()
kullanıldığında, yüksek user-blocking
önceliği nedeniyle devam etme olasılığı da yüksektir. Bu nedenle, scheduler.yield()
daha yaygın bir şekilde kullanıma sunulana kadar bunu bir alternatif olarak kullanabilirsiniz.
setTimeout()
(veya priority: 'user-visible'
ile scheduler.postTask()
kullanıldığında ya da açık bir priority
olmadan) sıranın arkasındaki görevi planlar ve beklemedeki diğer görevlerin devam etmeden önce çalıştırılmasını sağlar.
isInputPending()
ile girişte verim verin
Tarayıcı Desteği
- 87
- 87
- x
- x
isInputPending()
API, kullanıcının bir sayfayla etkileşim kurmaya çalışıp çalışmadığını kontrol etmenin bir yolunu sağlar ve yalnızca bir giriş beklemedeyse sonuç verir.
Bu, bekleyen giriş yoksa JavaScript'in devam edip görev sırasının sonunda ilerlemek yerine devam etmesini sağlar. Bu, aksi halde ana iş parçacığına geri dönmeyebilecek siteler için Gönderim Amacı bölümünde ayrıntılı olarak açıklandığı gibi etkileyici performans iyileştirmeleriyle sonuçlanabilir.
Ancak bu API'nin kullanıma sunulmasından bu yana, özellikle INP'nin kullanıma sunulmasından sonra verim konusundaki anlayışımız arttı. Artık bu API'yi kullanmanızı önermeyiz ve bunun yerine, girinin beklemede olup olmamasından bağımsız olarak işlem yapılmasını öneririz. Önerilerdeki bu değişikliğin çeşitli nedenleri vardır:
- API, kullanıcının etkileşimde bulunduğu bazı durumlarda yanlış bir şekilde
false
döndürebilir. - Görevlerin verilmesi gereken tek durum giriş değildir. Animasyonlar ve diğer düzenli kullanıcı arayüzü güncellemeleri, duyarlı bir web sayfası sağlamak için eşit derecede önemli olabilir.
- Bu endişeleri gidermek için
scheduler.postTask()
vescheduler.yield()
gibi daha kapsamlı ve getiri sağlayan API'ler kullanıma sunulmuştur.
Sonuç
Görevleri yönetmek zordur, ancak bunu yapmak sayfanızın kullanıcı etkileşimlerine daha hızlı yanıt vermesine yardımcı olur. Kullanım alanınıza bağlı olarak görevleri yönetmek ve önceliklendirmek için çeşitli teknikler vardır. Tekrarlamak gerekirse, görevleri yönetirken göz önünde bulundurmanız gereken temel unsurlar şunlardır:
- Kritik, kullanıcı odaklı görevler için ana iş parçacığına dönün.
scheduler.yield()
ile denemeler yapabilirsiniz.postTask()
ile görevlere öncelik verin.- Son olarak, fonksiyonlarınızda mümkün olduğunca az işlem yapın.
Bu araçlardan biri veya birkaçını kullandığınızda, uygulamanızdaki işi kullanıcının ihtiyaçlarına öncelik verecek şekilde yapılandırabilmeniz ve daha az kritik işlerin yapılmasını sağlarsınız. Bu, uygulamayı daha duyarlı ve kullanımı daha keyifli hale getirerek kullanıcı deneyimini iyileştirir.
Bu dokümanı teknik olarak incelediği için Philip Walton'a özel teşekkür ederiz.
Küçük resim, Amirali Mirhashemian'ın izniyle Unsplash'ten alınmıştır.