Web çalışanlarındaki JavaScript modülleri sayesinde, ağır işleri arka plan iş parçacıklarına taşımak artık daha kolay.
JavaScript tek iş parçacıklıdır. Bu nedenle, aynı anda yalnızca bir işlem gerçekleştirebilir. Bu yöntem sezgiseldir ve web'deki birçok durumda iyi sonuç verir ancak veri işleme, ayrıştırma, hesaplama veya analiz gibi ağır görevler yapmamız gerektiğinde sorunlu hale gelebilir. Web'de daha karmaşık uygulamalar sunuldukça çok iş parçacıklı işlemeye olan ihtiyaç da artıyor.
Web platformunda iş parçacığı oluşturma ve paralellik için temel öğe Web Workers API'dir. Worker'lar, iş parçacıkları arası iletişim için mesaj iletme API'si sunan işletim sistemi iş parçacıklarının üzerinde yer alan basit bir soyutlamadır. Bu özellik, maliyetli hesaplamalar yaparken veya büyük veri kümeleri üzerinde çalışırken son derece faydalı olabilir. Ana iş parçacığının sorunsuz çalışmasına olanak tanırken maliyetli işlemlerin bir veya daha fazla arka plan iş parçacığında gerçekleştirilmesini sağlar.
Çalışan kullanımına ilişkin tipik bir örneği burada bulabilirsiniz. Bu örnekte, çalışan komut dosyası ana iş parçacığından gelen mesajları dinler ve kendi mesajlarını geri göndererek yanıt verir:
page.js:
const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
console.log(e.data);
});
worker.postMessage('hello');
worker.js:
addEventListener('message', e => {
if (e.data === 'hello') {
postMessage('world');
}
});
Web Worker API, on yılı aşkın süredir çoğu tarayıcıda kullanılabilir. Bu durum, çalışanların mükemmel tarayıcı desteğine sahip olduğu ve iyi optimize edildiği anlamına gelse de JavaScript modüllerinden çok daha eski oldukları anlamına da gelir. Çalışanlar tasarlanırken modül sistemi olmadığı için, koda çalışan yükleme ve komut dosyası oluşturma API'si, 2009'da yaygın olan senkron komut dosyası yükleme yaklaşımlarına benzer şekilde kalmıştır.
Geçmiş: klasik çalışanlar
Worker oluşturucusu, belge URL'sine göreli olan bir klasik
script URL'si alır. Yeni worker örneğine hemen bir referans döndürür. Bu referans, mesajlaşma arayüzünün yanı sıra worker'ı hemen durdurup yok eden bir terminate()
yöntemini de kullanıma sunar.
const worker = new Worker('worker.js');
Web çalışanlarında ek kod yüklemek için importScripts()
işlevi kullanılabilir ancak her komut dosyasını getirmek ve değerlendirmek için çalışanın yürütülmesi duraklatılır. Ayrıca, klasik bir <script>
etiketi gibi genel kapsamdaki komut dosyalarını da yürütür. Bu da bir komut dosyasındaki değişkenlerin başka bir komut dosyasındaki değişkenler tarafından üzerine yazılabileceği anlamına gelir.
worker.js:
importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
postMessage(sayHello());
});
greet.js:
// global to the whole worker
function sayHello() {
return 'world';
}
Bu nedenle, web çalışanları geçmişte bir uygulamanın mimarisi üzerinde büyük bir etki yaratmıştır. Geliştiriciler, modern geliştirme uygulamalarından vazgeçmeden web çalışanlarını kullanabilmek için akıllı araçlar ve geçici çözümler oluşturmak zorunda kaldı. Örneğin, webpack gibi paketleyiciler, kod yükleme için importScripts()
kullanan oluşturulmuş koda küçük bir modül yükleyici uygulaması yerleştirir ancak değişken çakışmalarını önlemek ve bağımlılık içe aktarma ve dışa aktarma işlemlerini simüle etmek için modülleri işlevlere sarar.
Modül çalışanlarını girin
Chrome 80'de, JavaScript modüllerinin ergonomi ve performans avantajlarını sunan, web çalışanları için yeni bir mod olan modül çalışanları kullanıma sunuluyor. Worker
oluşturucusu artık komut dosyası yükleme ve yürütme işlemlerini <script type="module">
ile eşleşecek şekilde değiştiren yeni bir {type:"module"}
seçeneğini kabul ediyor.
const worker = new Worker('worker.js', {
type: 'module'
});
Modül çalışanları standart JavaScript modülleri olduğundan içe ve dışa aktarma ifadelerini kullanabilirler. Tüm JavaScript modüllerinde olduğu gibi, bağımlılıklar belirli bir bağlamda (ana iş parçacığı, çalışan vb.) yalnızca bir kez yürütülür ve gelecekteki tüm içe aktarmalar, zaten yürütülmüş olan modül örneğine referans verir. JavaScript modüllerinin yüklenmesi ve yürütülmesi de tarayıcılar tarafından optimize edilir. Bir modülün bağımlılıkları, modül yürütülmeden önce yüklenebilir. Bu sayede, tüm modül ağaçları paralel olarak yüklenebilir. Modül yükleme, ayrıştırılmış kodu da önbelleğe alır. Bu nedenle, ana iş parçacığında ve bir çalışanda kullanılan modüllerin yalnızca bir kez ayrıştırılması gerekir.
JavaScript modüllerine geçiş, çalışanların yürütülmesini engellemeden kodu geç yüklemek için dynamic import özelliğinin kullanılmasını da sağlar. İçe aktarılan modülün dışa aktarmaları döndürüldüğünden ve genel değişkenlere güvenilmediğinden, dinamik içe aktarma, bağımlılıkları yüklemek için importScripts()
kullanmaktan çok daha açık bir yöntemdir.
worker.js:
import { sayHello } from './greet.js';
addEventListener('message', e => {
postMessage(sayHello());
});
greet.js:
import greetings from './data.js';
export function sayHello() {
return greetings.hello;
}
Mükemmel performans sağlamak için eski importScripts()
yöntemi, modül çalışanlarında kullanılamaz. Çalışanları JavaScript modüllerini kullanacak şekilde değiştirme, tüm kodun katı modda yüklenmesi anlamına gelir. Diğer önemli bir değişiklik ise JavaScript modülünün üst düzey kapsamındaki this
değerinin undefined
olmasıdır. Klasik çalışanlarda ise değer, çalışanın genel kapsamıdır. Neyse ki her zaman genel kapsamı ifade eden bir self
global değişkeni olmuştur. Service worker'lar dahil olmak üzere tüm çalışan türlerinde ve DOM'da kullanılabilir.
Çalışanlara modulepreload
ile önceden yükleme yapma
Modül çalışanlarıyla birlikte gelen önemli bir performans iyileştirmesi, çalışanları ve bağımlılıklarını önceden yükleme özelliğidir. Modül çalışanları ile komut dosyaları standart JavaScript modülleri olarak yüklenir ve yürütülür. Bu da komut dosyalarının modulepreload
kullanılarak önceden yüklenebileceği ve hatta önceden ayrıştırılabileceği anlamına gelir:
<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">
<script>
addEventListener('load', () => {
// our worker code is likely already parsed and ready to execute!
const worker = new Worker('worker.js', { type: 'module' });
});
</script>
Önceden yüklenmiş modüller hem ana iş parçacığı hem de modül çalışanları tarafından kullanılabilir. Bu, her iki bağlamda da içe aktarılan modüller veya bir modülün ana iş parçacığında mı yoksa bir çalışanda mı kullanılacağını önceden bilmenin mümkün olmadığı durumlarda kullanışlıdır.
Daha önce, web çalışanı komut dosyalarını önceden yüklemek için kullanılabilen seçenekler sınırlıydı ve her zaman güvenilir değildi. Klasik çalışanlar, önceden yükleme için kendi "çalışan" kaynak türüne sahipti ancak hiçbir tarayıcı <link rel="preload" as="worker">
özelliğini uygulamadı. Sonuç olarak, web çalışanlarını önceden yüklemek için kullanılabilen birincil teknik, tamamen HTTP önbelleğine dayanan <link rel="prefetch">
öğesini kullanmaktı. Bu, doğru önbelleğe alma üstbilgileriyle birlikte kullanıldığında çalışan komut dosyasının indirilmesini beklemek zorunda kalmadan çalışan oluşturulmasını mümkün kılıyordu. Ancak modulepreload
bu tekniğin aksine, bağımlılıkların önceden yüklenmesini veya önceden ayrıştırılmasını desteklemiyordu.
Peki ya paylaşılan çalışanlar?
Paylaşılan çalışanlar, Chrome 83'ten itibaren JavaScript modülleri desteğiyle güncellendi. Özel çalışanlar gibi, {type:"module"}
seçeneğiyle paylaşılan bir çalışan oluşturmak artık çalışan komut dosyasını klasik bir komut dosyası yerine modül olarak yükler:
const worker = new SharedWorker('/worker.js', {
type: 'module'
});
JavaScript modülleri desteklenmeden önce SharedWorker()
oluşturucusu yalnızca bir URL ve isteğe bağlı bir name
bağımsız değişkeni bekliyordu. Bu, klasik paylaşılan çalışan kullanımı için çalışmaya devam edecektir. Ancak modül paylaşılan çalışanları oluşturmak için yeni options
bağımsız değişkeninin kullanılması gerekir. Kullanılabilir seçenekler, önceki name
bağımsız değişkeninin yerini alan name
seçeneği de dahil olmak üzere özel bir çalışan için kullanılan seçeneklerle aynıdır.
Hizmet çalışanı hakkında ne söylenebilir?
Hizmet çalışanı spesifikasyonu, modül çalışanlarıyla aynı {type:"module"}
seçeneğini kullanarak bir JavaScript modülünün giriş noktası olarak kabul edilmesini destekleyecek şekilde güncellendi ancak bu değişiklik henüz tarayıcılarda uygulanmadı. Bu işlem tamamlandıktan sonra, aşağıdaki kodu kullanarak JavaScript modülüyle bir hizmet çalışanı oluşturmak mümkün olur:
navigator.serviceWorker.register('/sw.js', {
type: 'module'
});
Şartname güncellendiğinden tarayıcılar yeni davranışı uygulamaya başladı. JavaScript modüllerini service worker'a getirmekle ilgili bazı ek zorluklar olduğundan bu işlem zaman alır. Hizmet çalışanı kaydı, bir güncellemenin tetiklenip tetiklenmeyeceğini belirlerken içe aktarılan komut dosyalarını önceden önbelleğe alınmış sürümleriyle karşılaştırmalıdır. Bu işlem, hizmet çalışanları için kullanılan JavaScript modülleri için uygulanmalıdır. Ayrıca, hizmet çalışanlarının güncellemeleri kontrol ederken belirli durumlarda komut dosyaları için önbelleği atlaması gerekir.
Ek kaynaklar ve daha fazla bilgi
- Özellik durumu, tarayıcı fikir birliği ve standardizasyon
- Orijinal modül çalışanları spesifikasyonuna ekleme
- Paylaşılan çalışanlar için JavaScript modülleri
- Service worker'lar için JavaScript modülleri: Chrome uygulama durumu