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.

Web, son 20 yılda birkaç stil ve resim içeren statik dokümanlardan karmaşık, dinamik uygulamalara doğru önemli ölçüde gelişti. Ancak büyük oranda değişmeyen bir şey var: 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ığı kullanırız (bazı istisnalar hariç).

Sonuç olarak, ana iş parçacığı inanılmaz derecede aşırı yüklenmiş hale geldi. Web uygulamaları karmaşık hale geldikçe ana iş parçacığı, performans açısından önemli bir darboğaz 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. Kullanıcılar web'e son derece kısıtlanmış özellikli telefonlardan yüksek güçlü, yüksek yenileme hızına sahip amiral gemisi cihazlara kadar giderek daha çeşitli cihazlardan eriştikçe bu öngörülemezlik de artacaktır.

Gelişmiş web uygulamalarının, insan algısı ve psikolojisi hakkındaki ampirik verilere dayanan Core Web Vitals gibi performans yönergelerini güvenilir bir şekilde karşılamasını istiyorsak kodumuzu ana iş parçacığında (OMT) çalıştırmanın yollarına ihtiyacımız var.

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ığındaki işleri işlemek için ayrı iş parçacığı oluşturmasına olanak tanıyarak ana iş parçacığından bir tür kaçış kapısı sağlar. Web işçilerinin kapsamı sınırlı olup DOM'a doğrudan erişim sağlamaz. Ancak, yapılması gereken ve ana iş parçacığının tıkanmasına neden olabilecek önemli bir iş varsa web işçileri çok faydalı olabilir.

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 iş parçacığının işlenmesi gereken daha az iş olduğunda kullanıcı etkileşimlerine daha hızlı yanıt verebilir.

Özellikle de başlangıç sırasında daha az ana ileti dizisi çalışması, uzun görevleri azaltarak Largest Contentful Paint (LCP) için potansiyel bir avantaj sağlar. LCP öğelerinin oluşturulması, sık ve yaygın LCP öğeleri olan metin veya resimlerin oluşturulması için ana iş parçacığı zamanı gerektirir. Genel olarak ana iş parçacığı çalışmasını azaltarak, sayfanızın LCP öğesinin, bir web işleyicisinin yerine alabileceği pahalı çalışmalar tarafından engellenme olasılığını azaltabilirsiniz.

Web işçileriyle mesaj dizileri 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 var olan ve 2012'den beri tüm büyük tarayıcılarda desteklenen web işçilerinden yaklaşık olarak benzer işlevler elde edebiliriz. Web çalışanları ana iş parçacığına paralel olarak çalışır ancak işletim sistemi iş parçacığı işlevinden farklı olarak 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 işleyiciyle iletişim kurun. Mesaj değerini postMessage çağrısında parametre olarak iletin ve ardından işleyiciye bir mesaj etkinliği dinleyicisi 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 işçisinde aynı postMessage API'yi kullanın ve ana mesaj dizisinde bir etkinlik dinleyicisi 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ğunu kabul etmek gerekir. Geçmişte web çalışanları, ağır bir işin tek bir parçasını ana iş parçacığının dışına 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, aynı zamanda iletideki işlemi de kodlamanız ve yanıtları isteklerle eşleştirmek için muhasebe tutmanız gerekir. Web işçilerinin daha yaygın olarak kullanılmamasının nedeni büyük olasılıkla 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 yapan 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 mesaj dizilerini destekleyen diğer programlama dillerinde olduğu gibi web işçileri ile ana mesaj dizisi arasında değişken paylaşmanıza olanak tanır.

Comlink'i bir web çalışanına içe aktararak ve ana iş parçacığına sunulacak bir işlev grubu tanımlayarak ayarlarsınız. Ardından Comlink'i ana mesaj dizisine aktarın, işleyiciyi sarmalayın ve kullanıma sunulan işlevlere erişin:

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 işçisindekiyle aynı şekilde çalışır. Tek fark, her işlevin değerin kendisi yerine bir değer için söz vermesidir.

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. Yine de, 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 uygulamadaki her şeyi düzenlemek için Vue veya React gibi bir kullanıcı arayüzü çerçevesine dayanmasıdır. Her şey bu çerçevenin bir bileşenidir ve dolayısıyla 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'te de tam olarak bu yaklaşım benimsenmiştir.

PROXX: OMT örnek olayı

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

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.
  • Web çalışanı, tamamen hesaplamalı olan oyun mantığını yönetir.

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 yoktur ve kullanıcının başka bir şey yapabilmesi için altı saniyeyi tam olarak 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ündeki 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 gösterildiği gibi, OMT, uygulamanızı daha geniş bir cihaz yelpazesinde güvenilir bir şekilde çalıştırır ancak uygulamanızı hızlandırmaz:

  • Yalnızca ana ileti dizisindeki işi azaltıyorsunuz, işlem sayısını azaltmıyorsunuz.
  • Web işleyici ile ana iş parçacığı arasındaki ek iletişim yükü, bazen işlemleri biraz daha 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şlemek için özgür olduğundan toplam bekleme süresi biraz daha uzun olsa bile daha az kare atlanır. Atlanan karelerde hata payı daha az olduğundan kullanıcının biraz beklemesini sağlamak, kare atlamaya tercih edilir. Kare atlama milisaniyeler içinde gerçekleşirken kullanıcının bekleme süresini algılaması için yüzlerce milisaniyeniz vardır.

Cihazlar arasında performansın öngörülemez olması nedeniyle OMT mimarisinin amacı, paralelleştirmenin performans avantajlarından ziyade riski azaltmaktır (uygulamanızı son derece değişken çalışma zamanı koşullarına karşı daha sağlam hale getirmek). Dayanıklılıktaki artış ve kullanıcı deneyimindeki iyileştirmeler, hızdaki küçük bir ödün karşılığında fazlasıyla değer sunar.

Araç kullanımıyla ilgili not

Web işçileri henüz yaygın olarak kullanılmadığından webpack ve Rollup gibi çoğu modül aracı, bunları kutudan çıkar çıkmaz desteklemez. (Ancak Parsel kullanılabilir.) Neyse ki web çalışanlarının webpack ve Rollup ile çalışmasını sağlayan eklentiler var:

Ö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, çoğu kullanıcının internete erişimi sınırlandırılmış cihazları desteklememiz gerekiyor. OMT, yüksek kaliteli cihaz 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ı vardır:

Web işçilerinin korkutucu 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.