Modül çalışanlarıyla web'de mesaj dizisi oluşturma

Web çalışanlarındaki JavaScript modülleri sayesinde ağır işleri arka plandaki ileti dizilerine 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 pratiktir 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 sorun yaratabilir. Web'de sunulan karmaşık uygulama sayısı arttıkça çoklu iş parçacıklı işleme ihtiyacı da artıyor.

Web platformunda iş parçacığı oluşturma ve paralellik için kullanılan temel temel öğe Web Workers API'dir. Çalışanlar, işletim sistemi iş parçacıklarının üzerindeki basit bir soyutlamadır. Bu iş parçacıkları, iş parçacıkları arası iletişim için mesaj iletme API'sini açığa çıkarır. Bu, maliyetli hesaplamalar yaparken veya büyük veri kümelerinde çalışırken son derece faydalı olabilir. Böylece, bir veya daha fazla arka plan iş parçacığı üzerinde pahalı işlemleri gerçekleştirirken ana iş parçacığının sorunsuz bir şekilde çalışmasına olanak tanır.

Bir çalışan komut dosyasının, ana iş parçacığından gelen mesajları dinlediği ve kendi mesajlarını geri göndererek yanıt verdiği, çalışan kullanımına dair tipik bir örnek aşağıda 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ılmaktadır. Bu, çalışanların mükemmel bir tarayıcı desteğine sahip olduğu ve iyi optimize edilmiş olduğu anlamına gelir. Ancak bu durum, aynı zamanda JavaScript modüllerinden uzun süre önce çıkmış olduğu anlamına da gelir. Çalışanlar tasarlanırken modül sistemi olmadığından, bir çalışana kod yüklemek ve komut dosyası oluşturmak için kullanılan API, 2009'da yaygın olarak kullanılan eşzamanlı komut dosyası yükleme yaklaşımlarına benzedi.

Tarih: klasik çalışanlar

Çalışan oluşturucu, doküman URL'sine bağlı bir klasik komut dosyası URL'si alır. Yeni çalışan örneğine hemen bir referans döndürür. Bu durumda hem mesajlaşma arayüzü hem de çalışanı hemen durduran ve yok eden bir terminate() yöntemi ortaya çıkar.

const worker = new Worker('worker.js');

importScripts() işlevi, web çalışanları içinde ek kod yüklemek için kullanılabilir. Ancak her bir komut dosyasını getirmek ve değerlendirmek için çalışanın yürütülmesini duraklatır. Ayrıca, klasik <script> etiketi gibi global kapsamda komut dosyalarını 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 etki yaratmıştır. Geliştiriciler, modern geliştirme uygulamalarından vazgeçmeden web çalışanlarını kullanmayı mümkün kılmak için zekice araçlar ve geçici çözümler üretmek zorunda kaldı. Örneğin, webpack gibi paketleyiciler, kod yükleme için importScripts() kullanan, oluşturulan 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şlevler içinde sarmalar.

Modül çalışanlarını girin

Chrome 80'de, web çalışanları için JavaScript modüllerinin ergonomisine ve performans avantajlarına sahip yeni bir mod kullanıma sunuluyor. Bu yeni mod, modül çalışanları olarak adlandırılıyor. 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, halihazırda yürütülen modül örneğine referans verir. JavaScript modüllerinin yüklenmesi ve çalıştırılması da 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 da modül ağaçlarının tamamının paralel olarak yüklenmesini sağlar. Modül yükleme, ayrıştırılmış kodu da önbelleğe alır. Yani, 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çmek, kodun çalıştırılmasını engellemeden kodu geç yükleme için dinamik içe aktarma özelliğinin kullanılmasını da sağlar. İçe aktarılan modülün dışa aktarımları global değişkenlere güvenmek yerine döndürüldüğünden, dinamik içe aktarma bağımlıları 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;
}

En iyi performansın sağlanması için modül çalışanları arasında eski importScripts() yöntemi kullanılamaz. Çalışanların JavaScript modüllerini kullanacak şekilde değiştirilmesi, tüm kodun katı modda yüklenmesi anlamına gelir. Dikkate değer bir başka değişiklik de JavaScript modülünün üst düzey kapsamındaki this değerinin undefined olmasıdır. Klasik çalışanlarda bu değer, çalışanın global kapsamıdır. Neyse ki, her zaman için global kapsama referans veren bir self global olmuştur. Service Worker dahil tüm çalışan türleri için ve DOM'de kullanılabilir.

modulepreload ile çalışanları önceden yükleyin

Modül çalışanlarının sağladığı önemli performans iyileştirmelerinden biri, çalışanları ve bağımlılıklarını önceden yükleyebilmektir. Modül çalışanları sayesinde komut dosyaları standart JavaScript modülleri olarak yüklenir ve yürütülür. Yani önceden yüklenebilir ve hatta modulepreload kullanılarak ö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 da kullanılabilir. Bu, her iki bağlamda içe aktarılan modüller için veya bir modülün ana iş parçacığında mı yoksa bir çalışanda mı kullanılacağının önceden bilinmediği durumlarda faydalıdır.

Önceden, web çalışanı komut dosyalarını önceden yüklemek için kullanılabilen seçenekler sınırlıydı ve gerektiği kadar 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"> uygulamadı. Sonuç olarak, web çalışanlarını önceden yüklemek için kullanılabilen birincil teknik, tamamen HTTP önbelleğine dayanan <link rel="prefetch"> kullanılmasıydı. Bu, doğru önbelleğe alma başlıklarıyla birlikte kullanıldığında, çalışan örneklendirmesinin çalışan komut dosyasını indirmek için beklemek zorunda kalmamasını sağladı. Ancak bu teknik, modulepreload'in aksine önceden yükleme bağımlılıklarını veya önceden ayrıştırmayı desteklemiyordu.

Paylaşılan çalışanlar ne olacak?

Paylaşılan çalışanlar, Chrome 83 itibarıyla JavaScript modülleri desteğiyle güncellenmiştir. Özel çalışanlar gibi, {type:"module"} seçeneğiyle paylaşılan bir çalışan oluşturulduğunda artık çalışan komut dosyası klasik komut dosyası yerine modül olarak yüklenir:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

JavaScript modüllerinin desteklenmesinden önce SharedWorker() oluşturucusu yalnızca bir URL ve isteğe bağlı bir name bağımsız değişkeni bekliyordu. Bu yöntem, klasik paylaşılan çalışan kullanımında da çalışmaya devam edecektir ancak modül paylaşımlı çalışanların oluşturulması 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ışanla aynıdır.

Service Worker'ın durumu nedir?

Service Worker spesifikasyonu, JavaScript modülünün giriş noktası olarak kabul edilmesini destekleyecek şekilde modül çalışanlarıyla aynı {type:"module"} seçeneği kullanılarak önceden güncellenmiştir, ancak bu değişiklik tarayıcılara henüz uygulanmamıştır. Bu gerçekleştikten sonra, aşağıdaki kodla JavaScript modülü kullanarak Service Worker'ı örneklendirebilirsiniz:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

Spesifikasyon güncellendiği için tarayıcılar yeni davranışı uygulamaya başladı. Bu işlem zaman alır çünkü JavaScript modüllerinin Service Worker'a alınmasıyla ilgili bazı ek komplikasyonlar söz konusudur. Service Worker kaydının, 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ırması gerekir. Bu yöntemin, hizmet çalışanları için kullanıldığında JavaScript modülleri için uygulanması gerekir. 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