JavaScript'i tarayıcının ana iş parçacığı dışında çalıştırmak için web çalışanlarını kullanın

Ana iş parçacığı dışı bir mimari, uygulamanızın güvenilirliğini ve kullanıcı deneyimini önemli ölçüde iyileştirebilir.

Geçtiğimiz 20 yılda web, sadece birkaç stil ve görsel içeren statik dokümanlardan karmaşık ve dinamik uygulamalara kadar büyük bir dönüşüm geçirdi. Ancak, büyük ölçüde değişmeyen bir şey var: Sitelerimizi oluşturma ve JavaScript'imizi çalıştırma işlemlerini yapmak için tarayıcı sekmesi başına yalnızca bir ileti dizimiz (bazı istisnalar hariç) var.

Sonuç olarak, ana ileti dizisi aşırı derecede aşırı iş yüküne neden oldu. Web uygulamalarının karmaşıklık düzeyi arttıkça, ana iş parçacığı performans açısından önemli bir performans sorunu haline gelir. Cihaz özelliklerinin performans üzerinde büyük bir etkisi olduğundan, belirli bir kullanıcı için ana iş parçacığında kod çalıştırmak için gereken süre neredeyse tamamen tahmin edilemez. Bu öngörülemezlik, yalnızca kullanıcılar son derece kısıtlı özellikli telefonlardan yüksek güçlü, yüksek yenileme hızına sahip ana makinelere kadar gittikçe çeşitlilik gösteren bir dizi cihazdan web'e eriştikçe artacak.

Gelişmiş web uygulamalarının, insan algısı ve psikolojisi hakkındaki deneysel verilere dayanan Core Web Vitals gibi performans yönergelerine güvenilir bir şekilde uymasını istiyorsak, kodumuzu ana iş parçacığı (OMT) dışında yürütmek için yöntemlere ihtiyacımız vardır.

Neden web çalışanları?

JavaScript, varsayılan olarak ana iş parçacığında görevleri çalıştıran tek iş parçacıklı bir dildir. Bununla birlikte, web çalışanları, geliştiricilerin ana iş parçacığındaki işleri halletmek için ayrı iş parçacıkları oluşturmasına olanak tanıyarak ana iş parçacığından bir tür kaçış yolu sağlar. Web çalışanlarının kapsamları sınırlıdır ve DOM'ye doğrudan erişim imkanı sunmazlar. Ancak ana iş parçacığını bunaltacak büyük miktarda iş yapılması gerektiğinde çok faydalı olabilirler.

Core Web Vitals'ın söz konusu olduğu durumlarda, işleri ana iş parçacığı üzerinde yürütmek faydalı olabilir. Özellikle, ana iş parçacığından web çalışanlarına yapılan işlerin boşaltılması, ana iş parçacığıyla ilgili anlaşmazlığı azaltarak sayfanın Sonraki Boyamayla Etkileşim (INP) duyarlılık metriğini iyileştirebilir. Ana ileti dizisinde işlenmesi gereken iş miktarı azaldığında kullanıcı etkileşimlerine daha hızlı yanıt verebilir.

Özellikle başlatma sırasında daha az ana iş parçacığı işi, uzun görevleri azaltarak Largest Contentful Paint (LCP) için potansiyel bir avantaj sağlar. LCP öğesi oluşturmak için ana iş parçacığı süresi gerekir. Sık kullanılan ve yaygın LCP öğeleri olan metin veya resimleri oluşturmak için kullanılır. Ana iş parçacığının genel olarak iş yükünü azaltarak sayfanızın LCP öğesinin, bir web çalışanının yapabileceği pahalı işlerle engellenme olasılığını azaltabilirsiniz.

Web çalışanlarıyla ileti dizisi oluşturma

Diğer platformlar, paralel çalışmayı genellikle bir iş parçacığına programınızın geri kalanına paralel olarak çalışan bir işlev sağlamanıza olanak tanıyarak destekler. Aynı değişkenlere her iki iş parçacığından da erişebilirsiniz. Bu paylaşılan kaynaklara erişim, ırk koşullarını önlemek için karşılıklı dışlamalar ve semaforlarla senkronize edilebilir.

JavaScript'te, 2007'den beri kullanımda olan ve 2012'den beri başlıca tüm tarayıcılarda desteklenen web çalışanlarından aşağı yukarı benzer işlevler alabiliriz. Web çalışanları, ana iş parçacığıyla paralel olarak çalışır ancak işletim sistemi iş parçacıklarının aksine değişkenleri paylaşamaz.

Web çalışanı oluşturmak için çalışan oluşturucuya bir dosya iletin. Bu işlem, dosyayı ayrı bir iş parçacığında çalıştırmaya başlar:

const worker = new Worker("./worker.js");

postMessage API'yi kullanarak mesaj göndererek web çalışanıyla iletişim kurun. Mesaj değerini postMessage çağrısına parametre olarak iletin ve ardından çalışana bir mesaj etkinliği işleyici ekleyin:

main.js

const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);

worker.js

addEventListener('message', event => {
  const [a, b] = event.data;

  // Do stuff with the message
  // ...
});

Bir mesajı ana ileti dizisine geri göndermek için web çalışanında aynı postMessage API'sini kullanın ve ana iş parçacığında bir etkinlik işleyici ayarlayın:

main.js

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

worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
  console.log(event.data);
});

worker.js

addEventListener('message', event => {
  const [a, b] = event.data;

  // Do stuff with the message
  postMessage(a + b);
});

Kabul etmek gerekir ki, bu yaklaşım biraz sınırlıdır. Geçmişte web çalışanları genellikle tek bir ağır işi ana iş parçacığından taşımak için kullanılıyordu. Birden çok işlemi tek bir web çalışanıyla yürütmeye çalışmak hızla zorlanır. Yalnızca parametreleri değil, mesajdaki işlemi de kodlamanız ve yanıtları isteklerle eşleştirmek için muhasebe tutmanız gerekir. Web çalışanlarının yaygın olarak benimsenmemesinin nedeni muhtemelen bu karmaşıklıktır.

Ancak ana iş parçacığı ile web çalışanları arasındaki iletişim zorluğunun bir kısmını ortadan kaldırabilseydik bu model birçok kullanım alanına mükemmel bir şekilde uyum sağlayabilir. Neyse ki tam da bunu sağlayan bir kitaplık var.

Comlink, postMessage ayrıntılarını düşünmek zorunda kalmadan web çalışanlarını kullanmanıza olanak tanıyan bir kitaplıktır. Comlink, neredeyse iş parçacığı oluşturmayı destekleyen diğer programlama dilleri gibi, web çalışanları ve ana iş parçacığı arasında değişkenleri paylaşmanıza olanak tanır.

Comlink'u bir web çalışanına aktararak ve ana iş parçacığına sunulacak işlev grubu tanımlayarak ayarlarsınız. Daha sonra ana iş parçacığında Comlink'u içe aktarır, çalışanı sarmalar ve açığa çıkan işlevlere erişim sağlarsınız:

worker.js

import {expose} from 'comlink';

const api = {
  someMethod() {
    // ...
  }
}

expose(api);

main.js

import {wrap} from 'comlink';

const worker = new Worker('./worker.js');
const api = wrap(worker);

Ana iş parçacığındaki api değişkeni, web çalışanındakiyle aynı şekilde davranır. Ancak her işlev değerin kendisi yerine bir değer için taahhüt döndürür.

Bir web çalışanına hangi kodu taşımanız gerekir?

Web çalışanlarının DOM'ye ve WebUSB, WebRTC veya Web Audio gibi birçok API'ye erişimi yoktur. Bu nedenle, uygulamanızın bu tür bir erişime dayanan parçalarını bir çalışana yerleştiremezsiniz. Buna rağmen, bir çalışana taşınan her küçük kod parçası, kullanıcı arayüzünü güncellemek gibi olması gereken öğeler için ana iş parçacığında daha fazla boşluk satın alır.

Web geliştiricilerinin karşılaştığı sorunlardan biri, çoğu web uygulamasının içindeki her şeyi düzenlemek için Vue veya React gibi bir kullanıcı arayüzü çerçevesine güvenmesidir. her şey çerçevenin bir bileşenidir ve doğası gereği DOM'ye bağlıdır. Bu durum, OMT mimarisine geçiş yapmayı zorlaştırabilir.

Ancak kullanıcı arayüzü ile ilgili endişelerin durum yönetimi gibi diğer endişelerden ayrı olduğu bir modele geçersek çerçeve tabanlı uygulamalarda bile web çalışanları son derece yararlı olabilir. PROXX konusunda da tam olarak bu yaklaşımı benimsedik.

PROXX: OMT örnek olayı

Google Chrome ekibi, çevrimdışı çalışma ve ilgi çekici bir kullanıcı deneyimi sunma gibi Progresif Web Uygulaması gereksinimlerini karşılayan bir Mayın Tarlası klonu olarak PROXX'i geliştirdi. Ne yazık ki oyunun ilk sürümleri, özellikli telefonlar gibi kısıtlı cihazlarda kötü bir performans sergiledi. Bu da ekibin ana iş parçacığının bir performans sorunu olduğunu fark etmesine neden oldu.

Ekip oyunun görsel durumunu mantığından ayırmak için web çalışanlarını kullanmaya karar verdi:

  • Animasyonların ve geçişlerin oluşturulması ana iş parçacığı tarafından işlenir.
  • Bir web çalışanı, tamamen bilişimsel olan oyun mantığını ele alır.
ziyaret edin.

OMT'nin, PROXX'in özellikli telefon performansı üzerinde ilginç etkileri oldu. OMT olmayan sürümde, kullanıcı arayüzü etkileşimde bulunduktan sonra kullanıcı arayüzü altı saniye boyunca donar. Geri bildirim gönderilmez ve kullanıcının başka bir işlem yapabilmek için altı saniyenin tamamını beklemesi gerekir.

PROXX'in OMT olmayan sürümünde kullanıcı arayüzü yanıt süresi.

Ancak OMT sürümünde oyunun kullanıcı arayüzü güncellemesinin tamamlanması on iki saniye sürer. Bu bir performans kaybı gibi görünse de aslında kullanıcıya daha fazla geri bildirim verilmesini sağlıyor. Yavaşlama, uygulamanın OMT olmayan sürüme göre daha fazla kare göndermesi ve hiçbir kareyi göndermemesi nedeniyle yaşanır. Böylece kullanıcı bir şeylerin olduğunu bilir ve kullanıcı arayüzü güncellendiğinde oyuna devam edebilir. Bu da oyunu çok daha iyi hissettirir.

PROXX'in OMT sürümünde kullanıcı arayüzü yanıt süresi.

Bu, bilinçli bir zahmettir. Kısıtlı cihazların kullanıcılarına, ileri teknoloji cihaz kullanıcılarını cezalandırmadan daha iyi hissedilen bir deneyim sunarız.

OMT mimarisinin etkileri

PROXX örneğinde de gösterildiği gibi OMT, uygulamanızın daha geniş bir cihaz yelpazesinde güvenilir bir şekilde çalışmasını sağlar ancak uygulamanızı hızlandırmaz:

  • Yalnızca ana ileti dizisindeki işi azaltıyorsunuz, işlem sayısını azaltmıyorsunuz.
  • Web çalışanı arasındaki ek iletişim ek yükü ve ana iş parçacığı bazen işleri biraz yavaşlatabilir.

Ödün verilecek noktaları göz önünde bulundurun

Ana iş parçacığı, JavaScript çalışırken kaydırma gibi kullanıcı etkileşimlerini işleyebildiğinden toplam bekleme süresi marjinal şekilde daha uzun olsa da atlanan kare sayısı daha az olur. Atlanan kareler için hata payı daha küçük olduğundan, kullanıcının bir kareyi bırakması daha tercih edilir: Bir karenin düşürülmesi milisaniye cinsinden, kullanıcı ise bekleme süresini algılamadan yüzlerce milisaniye sürer.

Farklı cihazlardaki performansın tahmin edilememesi nedeniyle OMT mimarisinin amacı aslında riski azaltmaktır. Paralelleştirmenin performans avantajları değil, yüksek değişken çalışma zamanı koşulları karşısında uygulamanızı daha güçlü hale getirmektir. Dirençteki artış ve kullanıcı deneyiminde yapılan iyileştirmeler, hızda küçük bir ödün vermeden daha değerli.

Araç kullanımıyla ilgili not

Web çalışanları henüz ana akım değildir. Bu nedenle, webpack ve Rollup gibi çoğu modül aracı, bunları kullanıma hazır şekilde desteklemez. (Ancak Parsel kullanılabilir.) Neyse ki, webpack ve Rollup ile çalışmak için web çalışanlarını destekleyen eklentiler vardır:

Özet

Uygulamalarımızın mümkün olduğunca güvenilir ve erişilebilir olmasını sağlamak için, özellikle de giderek küresel hale gelen pazar yerlerinde, kısıtlamalı cihazları desteklememiz gerekiyor. Kullanıcıların çoğu, web'e bu cihazlar üzerinden erişiyor. OMT, ileri teknoloji cihazların kullanıcılarını olumsuz etkilemeden bu tür cihazlarda performansı artırmanın umut verici bir yolunu sunuyor.

Ayrıca, OMT'nin ikincil avantajları vardır:

  • JavaScript yürütme maliyetlerini ayrı bir iş parçacığına taşır.
  • Ayrıştırma maliyetlerini değiştirir, yani kullanıcı arayüzü daha hızlı başlatılabilir. Bu, First Contentful Paint'i azaltabilir ve hatta Etkileşime Kalan Süre'yi Bu da gelirinizin artmasını Lighthouse puanı.

Web işçilerinin ürkütücü olması gerekmez. Comlink gibi araçlar, çalışanların iş yükünü hafifletir ve onları çok çeşitli web uygulamaları için uygun bir seçim haline getirir.

James Peacock'ın Unsplash tarafından sağlanan lokomotif resim.