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ışında 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üklenmeye başladı. Web uygulamaları 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ı büyük ölçüde etkilediğinden, belirli bir kullanıcı için ana iş parçacığında kodun çalıştırılmasının ne kadar süreceği 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ştiğinden bu öngörülemezlik yalnızca 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.

Web işçileri neden kullanılır?

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şturmaları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 önemli bir iş varsa ve bu iş ana iş parçacığının yükünü artıracaksa web işçileri çok yararlı olabilir.

Core Web Vitals ile ilgili olarak, ana iş parçacığında çalışma yürütmek yararlı olabilir. Özellikle ana iş parçacığındaki işleri web çalışanlarına aktarmak, ana iş parçacığı için çekişmeyi azaltabilir. Bu da sayfanın Interaction to Next Paint (INP) duyarlılığı 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şlatma 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ı LCP öğesinin bir web işleyicinin yerine alabileceği pahalı çalışmalar tarafından engellenmesinin olasılığını azaltabilirsiniz.

Web işçileriyle mesaj dizileri 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 atamanıza izin vererek paralel çalışmayı destekler. Her iki iş parçacığında da aynı değişkenlere erişebilirsiniz. Ayrıca, yarış koşullarını önlemek için bu paylaşılan kaynaklara erişim, mutex'ler 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 gönderin. Bu dosya, ayrı bir mesaj dizisinde çalışmaya 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ğu kabul edilmelidir. 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. Tek bir web işleyiciyle birden fazla işlemi yönetmeye çalışmak çok çabuk karmaşık hale gelir: Mesajda yalnızca parametreleri değil, işlemi de kodlamanız ve yanıtları isteklerle eşleştirmek için muhasebe yapmanız gerekir. Web işçilerinin daha yaygın olarak kullanılmamasının nedeni muhtemelen bu karmaşıklıktır.

Ancak ana iş parçacığı ile web işçileri arasındaki iletişimin bazı zorluklarını ortadan kaldırabilirsek bu model birçok kullanım alanı için mükemmel bir seçim olabilir. Neyse ki tam da bunu yapan bir kitaplık var.

Comlink, postMessage ayrıntılarını düşünmek zorunda kalmadan web işçilerini kullanmanıza olanak tanımayı amaçlayan 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 dizi işlev tanımlayarak ayarlarsınız. Ardından, Comlink'i ana mesaj dizisine aktarın, işçiyi 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. Bunun tek farkı, her işlevin değerin kendisi yerine değer için bir söz vermesidir.

Hangi kodu bir web işleyicisine taşımalısınız?

Web işçileri, DOM'a ve WebUSB, WebRTC veya Web Audio gibi birçok API'ye erişemez. Bu nedenle, uygulamanızın bu tür erişime dayalı parçalarını bir işçiye yerleştiremezsiniz. Yine de, bir işleyiciye taşınan her küçük kod parçası, ana iş parçacığında kullanıcı arayüzünü güncelleme gibi zorunlu olan işlemler için daha fazla yer açar.

Web geliştiricilerinin karşılaştığı sorunlardan biri, çoğu web uygulamasının uygulamadaki her şeyi koordine etmek için Vue veya React gibi bir kullanıcı arayüzü çerçevesine dayanmasıdır. Her şey çerçevenin bir bileşeni olduğundan, DOM'a bağlıdır. Bu durum, OMT mimarisine geçişi zorlaştırıyor.

Ancak kullanıcı arayüzü sorunlarının durum yönetimi gibi diğer sorunlardan ayrıldığı bir modele geçersek web işçileri, çerçeve tabanlı uygulamalarda bile oldukça 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 işçilerini kullanmaya karar verdi:

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

OMT, PROXX'in özellikli telefon performansında ilginç etkilere neden oldu. OMT olmayan sürümde, kullanıcı arayüzle etkileşime geçtikten 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ündeki 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ürüyor. Bu durum performans kaybı gibi görünse de aslında kullanıcıya daha fazla geri bildirim gönderilmesine yol açar. Uygulama, hiç çerçeve göndermeyen OMT dışı sürümden daha fazla çerçeve gönderdiği için yavaşlama yaşanır. Bu sayede kullanıcı, bir şeylerin olduğunu bilir ve kullanıcı arayüzü güncellenirken oynamaya devam edebilir. Bu da oyunun çok daha iyi hissettirilmesini sağlar.

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

Bu bilinçli bir değiş tokuştur: Yüksek kaliteli cihazların kullanıcılarını cezalandırmadan, sınırlı cihazların kullanıcılarına daha iyi hissedilen bir deneyim sunuyoruz.

OMT mimarisinin sonuçları

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:

  • İşi azaltmak yerine ana iş parçacığındaki işi taşıyorsunuz.
  • Web işleyici ile ana iş parçacığı arasındaki ek iletişim yükü, bazen işlemleri biraz daha yavaşlatabilir.

Artı ve eksileri değerlendirin

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ç seti hakkında not

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

Özet

Özellikle giderek küreselleşen bir pazarda uygulamalarımızın mümkün olduğunca güvenilir ve erişilebilir olmasını sağlamak için sınırlı cihazları desteklememiz gerekiyor. Dünya genelindeki çoğu kullanıcı, web'e bu cihazlardan erişiyor. 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ı da vardır:

Web işçilerinin korkutucu olması gerekmez. Comlink gibi araçlar, çalışanların yükünü hafifleterek onları çok çeşitli web uygulamaları için uygun bir seçenek haline getiriyor.