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ışı mimari, uygulamanızın güvenilirliğini ve kullanıcı deneyimini önemli ölçüde iyileştirebilir.

Son 20 yılda web, birkaç stil ve resim içeren statik belgelerden karmaşık, dinamik uygulamalara doğru önemli bir değişim geçirdi. Ancak bir şey büyük ölçüde değişmedi: Sitelerimizi oluşturma ve JavaScript'imizi çalıştırma işini yapmak için tarayıcı sekmesi başına yalnızca bir iş parçacığımız var (bazı istisnalar hariç).

Sonuç olarak ana iş parçacığı aşırı yüklenmiş. Web uygulamaları daha karmaşık hale geldikçe ana iş parçacığı, performans açısından önemli bir darboğaz haline gelir. Daha da kötüsü, cihaz özellikleri performans üzerinde büyük bir etkiye sahip olduğundan, belirli bir kullanıcı için ana iş parçacığında kod çalıştırmanın ne kadar süreceği neredeyse tamamen tahmin edilemez. Kullanıcılar web'e son derece kısıtlı özellikli telefonlardan yüksek güçlü, yüksek yenileme hızına sahip amiral gemisi cihazlara kadar her geçen gün daha çeşitli cihazlardan eriştiği için bu öngörülemezlik daha da artacaktır.

Gelişmiş web uygulamalarının, insan algısı ve psikolojisiyle ilgili ampirik verilere dayanan Core Web Vitals gibi performans yönergelerini güvenilir bir şekilde karşılamasını istiyorsak kodumuzu ana iş parçacığının dışında (OMT) yürütmenin yollarını bulmamız gerekir.

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. Ancak web çalışanları, geliştiricilerin ana iş parçacığı dışında işleri yürütmek 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 kapsamı sınırlı olsa ve DOM'a doğrudan erişim sunmasa da, ana iş parçacığını bunaltacak kadar çok iş yapılması gerekiyorsa son derece faydalı olabilirler.

Core Web Vitals söz konusu olduğunda, ana iş parçacığı dışında iş çalıştırmak faydalı olabilir. Özellikle, ana iş parçacığındaki işleri web çalışanlarına devretmek, ana iş parçacığı için çekişmeyi azaltabilir. Bu da sayfanın Interaction to Next Paint (INP) yanıt verme hızı metriğini iyileştirebilir. Ana iş parçacığının işleyeceği daha az iş olduğunda kullanıcı etkileşimlerine daha hızlı yanıt verebilir.

Ana iş parçacığı çalışmasının daha az olması (özellikle başlangıç sırasında), uzun süren görevleri azaltarak Largest Contentful Paint (LCP) için de potansiyel bir avantaj sağlar. LCP öğesi oluşturmak için ana iş parçacığı süresi gerekir. Bu süre, sık kullanılan ve yaygın LCP öğeleri olan metin veya resimlerin oluşturulması için kullanılır. Ana iş parçacığı çalışmasını genel olarak azaltarak sayfanızın LCP öğesinin, web çalışanının yapabileceği maliyetli işler tarafından engellenme olasılığını azaltabilirsiniz.

Web işçileriyle iş parçacığı oluşturma

Diğer platformlar genellikle bir iş parçacığına, programınızın geri kalanıyla paralel olarak çalışan bir işlev vermenize olanak tanıyarak paralel çalışmayı destekler. Her iki iş parçacığından da aynı değişkenlere erişebilirsiniz. Yarışma durumlarını önlemek için bu paylaşılan kaynaklara erişim, mutex'ler ve semaforlarla senkronize edilebilir.

JavaScript'te, 2007'den beri mevcut olan ve 2012'den beri tüm büyük tarayıcılarda desteklenen web çalışanlarından yaklaşık olarak benzer işlevler elde edebiliriz. Web çalışanları, ana iş parçacığıyla paralel olarak çalışır ancak işletim sistemi iş parçacığından farklı olarak değişkenleri paylaşamaz.

Web çalışanı oluşturmak için çalışan oluşturucuya bir dosya iletin. Bu dosya, ayrı bir iş parçacığında çalışmaya 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ında parametre olarak iletin ve ardından çalışan için bir mesaj etkinlik işleyicisi 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
  // ...
});

Ana mesaj dizisine geri mesaj göndermek için web çalışanında aynı postMessage API'yi kullanın ve ana mesaj dizisinde 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);
});

Bu yaklaşımın biraz sınırlı olduğu kabul edilebilir. 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. Tek bir web çalışanıyla birden fazla işlemi yönetmeye çalışmak kısa sürede zorlaşır: Yalnızca parametreleri değil, mesajdaki işlemi de kodlamanız ve yanıtları isteklerle eşleştirmek için kayıt tutmanız gerekir. Web çalışanlarının daha yaygın şekilde kullanılmamasının nedeni muhtemelen bu karmaşıklıktır.

Ancak ana iş parçacığı ile web çalışanları arasındaki iletişim zorluğunu biraz azaltabilirsek bu model birçok kullanım alanı için çok uygun olabilir. Neyse ki tam olarak bu işlevi yerine getiren 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, iş parçacığı oluşturmayı destekleyen diğer programlama dillerine benzer şekilde, web çalışanları ile ana iş parçacığı arasında değişkenleri paylaşmanıza olanak tanır.

Comlink'i bir web çalışanına aktararak ve ana iş parçacığına sunulacak bir dizi işlev tanımlayarak ayarlarsınız. Ardından, Comlink'i ana iş parçacığına aktarır, çalışanı sarmalar ve gösterilen işlevlere erişirsiniz:

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, her işlevin değerin kendisi yerine bir değer için söz döndürmesi dışında, web çalışanındaki değişkenle aynı şekilde davranır.

Hangi kodu bir web çalışanına taşımalısınız?

Web çalışanlarının DOM'a ve WebUSB, WebRTC veya Web Audio gibi birçok API'ye erişimi yoktur. Bu nedenle, uygulamanızın bu tür erişime dayalı parçalarını bir çalışana yerleştiremezsiniz. Yine de bir iş parçacığına taşınan her küçük kod parçası, ana iş parçacığında kullanıcı arayüzünü güncelleme gibi orada olması gereken işlemler için daha fazla alan sağlar.

Web geliştiricileri için sorunlardan biri, çoğu web uygulamasının uygulamadaki her şeyi düzenlemek için Vue veya React gibi bir kullanıcı arayüzü çerçevesine dayanmasıdır. Her şey çerçevenin bir bileşenidir ve bu nedenle doğası gereği DOM'a bağlıdır. Bu durum, OMT mimarisine geçişi zorlaştırabilir.

Ancak kullanıcı arayüzüyle ilgili konuların durum yönetimi gibi diğer konulardan ayrıldığı bir modele geçersek web çalışanları, çerçeve tabanlı uygulamalarda bile oldukça faydalı olabilir. PROXX'te de tam olarak bu yaklaşım benimsenmiştir.

PROXX: OMT örnek olay incelemesi

Google Chrome ekibi, PROXX'u çevrimdışı çalışma ve ilgi çekici bir kullanıcı deneyimi sunma gibi Progresif Web Uygulaması gereksinimlerini karşılayan bir Mayın Tarlası klonu olarak geliştirdi. Maalesef oyunun ilk sürümleri, özellikli telefonlar gibi kısıtlı cihazlarda düşük performans gösterdi. Bu durum, ekibin ana iş parçacığının bir darboğaz olduğunu fark etmesine yol açtı.

Ekip, oyunun görsel durumunu mantığından ayırmak için web worker'ları kullanmaya karar verdi:

  • Ana iş parçacığı, animasyonların ve geçişlerin oluşturulmasını yönetir.
  • Web çalışanı, tamamen hesaplamaya dayalı olan oyun mantığını işler.

OMT, PROXX'un özellikli telefon performansını ilginç bir şekilde etkiledi. OMT olmayan sürümde, kullanıcı etkileşiminden sonra kullanıcı arayüzü altı saniye boyunca donar. Geri bildirim yoktur ve kullanıcının başka bir işlem yapabilmesi için altı saniye boyunca beklemesi gerekir.

PROXX'un 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üncellemesini tamamlaması on iki saniye sürer. Bu durum performans kaybı gibi görünse de aslında kullanıcıya daha fazla geri bildirim verilmesini sağlar. Yavaşlama, uygulamanın OMT olmayan sürüme kıyasla daha fazla kare göndermesinden kaynaklanır. OMT olmayan sürüm hiç kare göndermez. Böylece kullanıcı bir şeylerin gerçekleştiğini bilir ve kullanıcı arayüzü güncellenirken oynamaya devam edebilir. Bu da oyunun çok daha iyi hissettirmesini sağlar.

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

Bu, bilinçli bir dengelemedir: Sınırlı cihazların kullanıcılarına, üst düzey cihazların kullanıcılarını cezalandırmadan daha iyi hissettiren bir deneyim sunuyoruz.

OMT mimarisinin etkileri

PROXX örneğinde 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ı daha hızlı hale getirmez:

  • İşi azaltmıyor, yalnızca ana iş parçacığından başka bir yere taşıyorsunuz.
  • Web çalışanı ile ana iş parçacığı arasındaki ek iletişim yükü bazen işlemleri biraz yavaşlatabilir.

Artıları ve eksileri değerlendirme

JavaScript çalışırken ana iş parçacığı, kaydırma gibi kullanıcı etkileşimlerini işleyebildiğinden toplam bekleme süresi biraz daha uzun olsa bile daha az kare atlanır. Kullanıcıyı biraz bekletmek, kare atlamaya tercih edilir. Çünkü kare atlamada hata payı daha küçüktür: Kare atlama milisaniyeler içinde gerçekleşirken, kullanıcının bekleme süresini algılaması için yüzlerce milisaniye gerekir.

Cihazlar arasındaki performansın öngörülemezliği nedeniyle OMT mimarisinin amacı, paralelleştirmenin performans avantajlarıyla ilgili değil, riski azaltmak, yani uygulamanızı son derece değişken çalışma zamanı koşullarına karşı daha sağlam hale getirmektir. Dayanıklılıktaki artış ve kullanıcı deneyimindeki iyileştirmeler, hızdaki küçük ödünlere fazlasıyla değer.

Araçlar hakkında bir not

Web çalışanları henüz yaygın olarak kullanılmadığından webpack ve Rollup gibi çoğu modül aracı bunları kullanıma hazır olarak desteklemez. (Parcel ise destekler.) Neyse ki web çalışanlarının webpack ve Rollup ile çalışmasını sağlayan eklentiler var:

Özet

Uygulamalarımızın özellikle giderek küreselleşen bir pazarda mümkün olduğunca güvenilir ve erişilebilir olmasını sağlamak için kısıtlı cihazları desteklememiz gerekir. Dünya genelinde çoğu kullanıcı web'e bu cihazlar üzerinden erişir. OMT, üst düzey cihazların kullanıcılarını olumsuz etkilemeden bu tür cihazlarda performansı artırmanın umut verici bir yolunu sunar.

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

  • JavaScript yürütme maliyetlerini ayrı bir iş parçacığına taşır.
  • Ayrıştırma maliyetlerini taşıdığı için kullanıcı arayüzü daha hızlı başlatılabilir. Bu, First Contentful Paint'i ve hatta Time to Interactive'i azaltarak Lighthouse puanınızı artırabilir.

Web çalışanlarının korkutucu olması gerekmez. Comlink gibi araçlar, çalışanların işini kolaylaştırarak onları çok çeşitli web uygulamaları için uygun bir seçenek haline getiriyor.