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ı olduğundan aynı anda yalnızca bir işlem gerçekleştirebilir. Bu, sezgiseldir ve web'deki birçok durumda iyi çalışır ancak veri işleme, ayrıştırma, hesaplama veya analiz gibi ağır görevleri yapmamız gerektiğinde sorunlu hale gelebilir. Web'de giderek 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 ana temel öğe Web Workers API'dir. Çalışanlar, iş parçacıkları arası iletişim için mesaj ileten bir API sunan işletim sistemi iş parçacıklarının üzerinde basit bir soyutlamadır. Bu, maliyetli hesaplamalar yaparken veya büyük veri kümeleri üzerinde çalışırken son derece yararlı olabilir. Bu sayede, bir veya daha fazla arka plan iş parçacığında maliyetli işlemler gerçekleştirilirken ana iş parçacığı sorunsuz bir şekilde çalışabilir.
Aşağıda, çalışan komut dosyasının ana iş parçacığından gelen mesajları dinlediği ve kendi mesajlarını göndererek yanıt verdiği tipik bir çalışan kullanımı örneği verilmiştir:
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ılabilmektedir. 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 tasarlandığında modül sistemi olmadığından, bir çalışana kod yükleme ve komut dosyaları oluşturma API'si, 2009'da yaygın olan eşzamanlı komut dosyası yükleme yaklaşımlarına benzer şekilde kalmıştır.
Geçmiş: Klasik çalışanlar
Worker oluşturucu, doküman URL'sine göre klasik komut dosyası URL'si alır. Yeni çalışan örneğine hemen bir referans döndürür. Bu referans, bir mesajlaşma arayüzünün yanı sıra çalışanı hemen durdurup yok eden bir terminate() yöntemi sunar.
const worker = new Worker('worker.js');
Web çalışanlarında ek kod yüklemek için bir importScripts() işlevi kullanılabilir ancak bu işlev, her komut dosyasını getirip değerlendirmek için çalışanın yürütülmesini duraklatır. Ayrıca, klasik bir <script> etiketi gibi global kapsamda komut dosyaları yürütür. Yani bir komut dosyasındaki değişkenlerin üzerine başka bir komut dosyasındaki değişkenler yazılabilir.
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 çok büyük bir etkiye sahipti. 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 ve dışa aktarmalarını simüle etmek için modülleri işlevlere sarmalar.
Modül çalışanlarını girin
Chrome 80'de, JavaScript modüllerinin ergonomi ve performans avantajlarını sunan, modül çalışanları adlı yeni bir web çalışanı modu kullanıma sunuluyor. Worker oluşturucu artık yeni bir {type:"module"} seçeneğini kabul ediyor. Bu seçenek, komut dosyası yükleme ve yürütme işlemini <script type="module"> ile eşleşecek şekilde değiştirir.
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 aktarma işlemleri, önceden yürütülen 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, ana iş parçacığında ve bir çalışan iş parçacığında kullanılan modüllerin yalnızca bir kez ayrıştırılması gerektiği anlamına gelir.
JavaScript modüllerine geçiş, çalışan yürütmesini engellemeden kodun geç yüklenmesi için dinamik içe aktarma kullanımını da sağlar. Dinamik içe aktarma, bağımlılıkları yüklemek için importScripts() kullanmaktan çok daha nettir.
Çünkü içe aktarılan modülün dışa aktarılanları, global değişkenlere güvenmek yerine döndürülür.
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;
}
Yüksek performans sağlamak için eski importScripts() yöntemi, modül çalışanlarında kullanılamaz. Çalışanların JavaScript modüllerini kullanacak şekilde değiştirilmesi, tüm kodların yüksek düzey modunda yüklendiği anlamına gelir. Dikkate değer bir diğer değişiklik de JavaScript modülünün üst düzey kapsamındaki this değerinin undefined, klasik çalışanlarda ise çalışanın global kapsamı olmasıdır. Neyse ki her zaman global kapsam için referans sağlayan bir self globali vardı. Hizmet çalışanları da dahil olmak üzere tüm çalışan türlerinde ve DOM'da kullanılabilir.
Önceden yüklenmiş çalışan sayısı: modulepreload
Modül çalışanlarıyla birlikte gelen önemli bir performans iyileştirmesi, çalışanları ve bağımlılıklarını önceden yükleme olanağıdır. Modül çalışanları sayesinde komut dosyaları standart JavaScript modülleri olarak yüklenip yürütülür. Bu sayede, modulepreload kullanılarak önceden yüklenebilir ve hatta önceden ayrıştırılabilir:
<!-- 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 iş parçacığında 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ın önceden yükleme için kendi "çalışan" kaynak türü vardı ancak hiçbir tarayıcı <link rel="preload" as="worker">'i uygulamadı. Bu nedenle, web çalışanlarını önceden yüklemek için kullanılabilen birincil teknik, tamamen HTTP önbelleğine dayanan <link rel="prefetch"> yöntemini kullanmaktı. Doğru önbelleğe alma başlıklarıyla birlikte kullanıldığında, çalışan örneklendirmesinin çalışan komut dosyasını indirmek için beklemesi gerekmez. Ancak modulepreload yönteminin aksine bu teknik, bağımlılıkların önceden yüklenmesini veya önceden ayrıştırmayı desteklemiyordu.
Paylaşılan çalışanlar için süreç nasıl işler?
Paylaşılan çalışanlar, Chrome 83'ten itibaren JavaScript modülleri için destekle güncellenmiştir. Özel çalışanlarda olduğu gibi, {type:"module"} seçeneğiyle paylaşılan bir çalışan oluşturulduğunda çalışan komut dosyası artık klasik komut dosyası yerine modül olarak yüklenir:
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 edecek ancak modül paylaşılan çalışanlar oluşturmak için yeni options bağımsız değişkeninin kullanılması gerekecek. Kullanılabilir seçenekler, önceki name bağımsız değişkeninin yerini alan name seçeneği de dahil olmak üzere özel çalışan için olanlarla aynıdır.
Peki ya hizmet çalışanı?
Hizmet çalışanı spesifikasyonu, giriş noktası olarak bir JavaScript modülünü kabul etmeyi destekleyecek şekilde güncellenmiştir. Bu işlem için modül çalışanlarıyla aynı {type:"module"} seçeneği kullanılır ancak bu değişiklik henüz tarayıcılarda uygulanmamıştır. Bu gerçekleştiğinde, aşağıdaki kodu kullanarak bir JavaScript modülüyle hizmet çalışanını örneklendirmek mümkün olacaktır:
navigator.serviceWorker.register('/sw.js', {
type: 'module'
});
Spesifikasyon güncellendiği için tarayıcılar yeni davranışı uygulamaya başlıyor. JavaScript modüllerini hizmet çalışanına eklemekle 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ı önceki önbelleğe alınmış sürümleriyle karşılaştırmalıdır. Bu, hizmet çalışanları için kullanıldığında JavaScript modüllerine uygulanmalıdır. Ayrıca hizmet çalışanlarının, güncellemeleri kontrol ederken belirli durumlarda komut dosyaları için önbelleği atlayabilmesi gerekir.
Ek kaynaklar ve daha fazla bilgi
- Özellik durumu, tarayıcı mutabakatı ve standardizasyon
- Orijinal modül çalışanları spesifikasyon eklemesi
- Paylaşılan çalışanlar için JavaScript modülleri
- Hizmet çalışanları için JavaScript modülleri: Chrome uygulama durumu