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

Web çalışanlarındaki JavaScript modülleriyle ağır işleri arka plan 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 sezgiseldir ve web'deki birçok durumda iyi sonuç verir, ancak veri işleme, ayrıştırma, hesaplama veya analiz gibi ağır işleri yapmamız gerektiğinde sorun yaratabilir. Web'de giderek daha fazla karmaşık uygulama sunuldukça, çoklu iş parçacıklı işleme ihtiyacı da giderek artıyor.

Web platformunda, iş parçacığı oluşturma ve paralelliğin temel unsuru Web Workers API'dir. Çalışanlar, 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, maliyetli hesaplamalar gerçekleştirirken veya büyük veri kümelerinde çalışırken bir ya da daha fazla arka plan iş parçacığı üzerinde pahalı işlemler gerçekleştirirken ana iş parçacığının sorunsuz şekilde çalışmasına olanak tanır.

Aşağıda, bir çalışan komut dosyasının ana ileti dizisinden gelen mesajları dinleyip kendi mesajlarını geri göndererek yanıt verdiği tipik bir çalışan kullanımına örnek 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ıldan uzun süredir çoğu tarayıcıda kullanılmaktadır. Bu, çalışanların mükemmel tarayıcı desteğine ve iyi optimize edilmiş olmalarına rağmen, aynı zamanda JavaScript modüllerini uzun süre önce kullandıkları anlamına da gelir. Çalışanlar tasarlanırken modüler bir sistem olmadığından, bir çalışana kod yükleme ve komut dosyası oluşturma API'si, 2009 yılında yaygın olarak kullanılan eşzamanlı komut dosyası yükleme yaklaşımlarıyla benzerlikte kalmıştır.

Geçmiş: klasik çalışanlar

Çalışan oluşturucu, doküman URL'sine bağlı bir klasik komut dosyası URL'si alır. Bu işlev, yeni çalışan örneğine anında bir referans döndürür. Bu durumda, çalışanı hemen durdurup kaldıran bir terminate() yöntemi ve mesajlaşma arayüzü görüntülenir.

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

Web çalışanları içinde ek kod yüklemek üzere bir importScripts() işlevi mevcuttur ancak her komut dosyasını getirmek ve değerlendirmek için çalışanın yürütülmesini duraklatır. Ayrıca, komut dosyalarını global kapsamda klasik <script> etiketi gibi yürütür. Diğer bir deyişle, bir komut dosyasındaki değişkenlerin üzerine başka bir komut dosyasındaki değişkenlerin üzerine 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 işçileri geçmişten beri bir uygulamanın mimarisine çok büyük bir etki getirmiştir. 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ı. Örnek olarak, web paketi 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ı önlemek ve bağımlılık içe ve dışa aktarımlarını simüle etmek için modülleri işlevlere sarmalar.

Modül çalışanları girin

Web çalışanlarına yönelik, JavaScript modüllerinin ergonomik ve performans avantajlarına sahip yeni bir mod, "modül çalışanları" olarak adlandırılan Chrome 80 sürümünde kullanıma sunuluyor. Worker oluşturucu 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 halihazırda yürütülmüş modül örneğine başvurur. JavaScript modüllerinin yüklenmesi ve yürütülmesi de tarayıcılar tarafından optimize edilir. 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üklenmesine olanak tanır. Modül yükleme, ayrıştırılan kodu da önbelleğe alır. Diğer bir deyişle, ana iş parçacığı ve bir çalışan içinde kullanılan modüllerin yalnızca bir kez ayrıştırılması gerekir.

JavaScript modüllerine geçmek, çalışanın yürütülmesini engellemeden geç yükleme kodu için dinamik içe aktarmanın kullanılabilmesini de sağlar. İçe aktarılan modülün dışa aktarma işlemleri genel değişkenlere bağlı kalmak yerine döndürüldüğünden, dinamik içe aktarma, bağımlılıkları yüklemek için importScripts() kullanmaktan çok daha açıktı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;
}

Mükemmel bir performans sağlamak için modül çalışanları arasında eski importScripts() yöntemi yoktur. Çalışanları JavaScript modüllerini kullanacak şekilde değiştirmek, tüm kodun katı kurallı modda yükleneceği anlamına gelir. Kayda değer bir başka 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 genel kapsamı olmasıdır. Neyse ki, her zaman küresel kapsama referans sağlayan bir self global olmuştur. Service Worker'lar dahil olmak üzere tüm çalışan türlerinde ve DOM'da kullanılabilir.

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

Modül çalışanları ile birlikte gelen önemli bir performans iyileştirmesi, çalışanları ve bağımlılıklarını önceden yükleyebilme yeteneğidir. Modül çalışanları sayesinde komut dosyaları standart JavaScript modülleri olarak yüklenir ve yürütülür. Diğer bir deyişle, 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 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ı önceden bilmenin mümkün olmadığı durumlarda yararlıdır.

Önceden, web çalışanı komut dosyalarını önceden yükleme seçenekleri sınırlıydı ve gerekli ölçüde güvenilir değildi. Klasik çalışanların önceden yükleme için kendi "çalışan" kaynak türü vardı, ancak <link rel="preload" as="worker"> herhangi bir tarayıcıda uygulanmadı. Bunun sonucunda, web çalışanlarının önceden yüklenmesi için kullanılabilen birincil teknik, tamamen HTTP önbelleğine dayalı <link rel="prefetch"> kullanmaktı. Doğru önbelleğe alma üst bilgileriyle birlikte kullanıldığında bu, çalışan komut dosyasının çalışan komut dosyasını indirmek için beklemek zorunda kalmasının önüne geçebildi. Ancak modulepreload'in aksine bu teknik, bağımlılıkları önceden yüklemeyi veya ön ayrıştırmayı desteklemiyordu.

Paylaşımlı çalışanlara ne olacak?

Paylaşılan çalışanlar, Chrome 83 sürümünden itibaren JavaScript modülleri desteğiyle güncellenmiştir. Özel çalışanlar gibi, {type:"module"} seçeneğiyle paylaşılan bir çalışan oluşturmak artık çalışan komut dosyasını klasik komut dosyası yerine modül olarak yüklüyor:

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

JavaScript modüllerini desteklemeden önce SharedWorker() oluşturucu, yalnızca bir URL ve isteğe bağlı name bağımsız değişkeni bekliyordu. Bu model, klasik paylaşılan çalışan kullanımında da ç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 sunulan seçeneklerle aynıdır.

Service Worker'ın durumu nedir?

Service Worker özelliği, modül çalışanları ile aynı {type:"module"} seçeneği kullanılarak JavaScript modülünü giriş noktası olarak kabul edilmesini destekleyecek şekilde güncellenmiş ancak bu değişiklik tarayıcılara henüz uygulanmadı. Bu olduğunda, JavaScript modülü kullanarak aşağıdaki kodu kullanarak Service Worker örneği oluşturabilirsiniz:

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

Spesifikasyon güncellendikten sonra, tarayıcılar yeni davranışı uygulamaya başlamıştır. JavaScript modüllerinin Service Worker'a taşınmasıyla ilişkili bazı ek komplikasyonlar olduğundan bu işlem zaman alır. Bir güncellemenin tetiklenip tetiklenmeyeceğine karar verilirken Service Worker kaydının, içe aktarılan komut dosyalarını önceki önbelleğe alınmış sürümleriyle karşılaştırması ve Service Worker'lar için kullanıldığında bu işlemin JavaScript modüllerinde uygulanması gerekir. Ayrıca hizmet çalışanlarının, belirli durumlarda güncellemeleri kontrol ederken komut dosyaları için önbelleği atlayabilmesi gerekir.

Ek kaynaklar ve ayrıntılı okuma