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

Surma
Surma

Geçtiğimiz 20 yılda web önemli ölçüde gelişerek birkaç stil ve resim içeren statik dokümanlar yerine karmaşık ve dinamik uygulamalara dönüştü. Ancak, büyük ölçüde 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ığımız (bazı istisnalar hariç).

Sonuç olarak, ana iş parçacığı inanılmaz ölçüde fazla çalışıldı. Web uygulamaları daha karmaşık hale geldikçe ana iş parçacığı performans açısından önemli bir dar boğaz haline gelir. Daha da kötüsü, 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 üzere gereken süre neredeyse tamamen öngörülemez. Bu öngörülemezlik ancak kullanıcılar, aşırı kısıtlı özellikli telefonlardan yüksek güçlü, yüksek yenileme hızına sahip ana makinelere kadar her geçen gün daha fazla farklı cihaz kullanarak web'e eriştikçe daha da artacak.

Karmaşık web uygulamalarının, insan algısı ve psikolojisiyle ilgili deneysel verilere dayanan Önemli Web Verileri gibi performans yönergelerini güvenilir bir şekilde karşılamasını istiyorsak kodumuzu ana iş parçacığından (OMT) yürütmek için çeşitli 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ını döndürmelerine izin vererek ana iş parçacığından bir tür kaçış yolu sunar. Web çalışanlarının kapsamı sınırlı olup DOM'ye doğrudan erişim imkanı sunmasa da ana iş parçacığının önüne geçilmesi gereken önemli miktarda iş yapılması gerekiyorsa bunlar çok faydalı olabilir.

Önemli Web Verileri söz konusu olduğunda, ana iş parçacığı üzerinden çalışma yürütmek faydalı olabilir. Özellikle, işin ana iş parçacığından web çalışanlarına aktarılması, ana iş parçacığı için çakışmayı azaltabilir. Bu da Sonraki Boyamayla Etkileşim (INP) ve İlk Giriş Gecikmesi (FID) gibi önemli yanıt verme metriklerini iyileştirebilir. Ana iş parçacığının işlenmesi gereken iş miktarı daha az olduğunda 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 da sunar. LCP öğesi oluşturmak için ana iş parçacığı süresi (sık ve yaygın LCP öğeleri olan metin veya resimleri oluşturmak için) gerekir. Ana iş parçacığı çalışmasını genel olarak 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ı ile mesaj dizisi oluşturma

Diğer platformlar, bir iş parçacığına programınızın geri kalanıyla paralel olarak çalışan bir işlev sağlamanıza olanak tanıyarak paralel çalışmayı genellikle destekler. Her iki iş parçacığında aynı değişkenlere erişebilirsiniz. Ayrıca, paylaşılan bu kaynaklara erişim, yarış koşullarını önlemek için mutex'ler ve semaforlarla senkronize edilebilir.

JavaScript'te, 2007'den beri mevcut olan ve 2012'den beri tüm önemli tarayıcılarda desteklenen web çalışanlarından aşağı yukarı benzer işlevler alabiliriz. Web çalışanları, ana iş parçacığına paralel olarak çalışır ancak OS iş parçacığı işlemlerinden farklı olarak değişkenleri paylaşamazlar.

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 aracılığıyla 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ış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
  // ...
});

Ana ileti dizisine bir mesaj göndermek için web çalışanında aynı postMessage API'sini kullanın ve ana ileti dizisinde bir etkinlik işleyici oluşturun:

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);
});

İtiraf edelim ki, bu yaklaşım kısmen sınırlı. Geçmişte, web çalışanları çoğunlukla tek bir ağır iş parçasını ana iş parçacığından taşımak için kullanılırdı. Tek bir web çalışanıyla birden çok işlem yapmaya çalışmak hızlı bir şekilde zor hale gelir: Sadece parametreleri değil, mesajdaki işlemi de kodlamanız ve isteklere verilen yanıtları eşleştirmek için muhasebe yapmanız gerekir. Bu karmaşıklık, büyük olasılıkla web çalışanlarının daha yaygın bir şekilde benimsenmemesinin nedenidir.

Ancak ana iş parçacığı ile web çalışanları arasındaki iletişimin zorluğundan bir kısmını ortadan kaldırabilirsek 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 hizmetinin ayrıntılarını düşünmek zorunda kalmadan web çalışanlarını kullanmanıza olanak tanıyan bir kitaplıktır. Comlink, tıpkı ileti dizisi özelliğini 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'i, bir web çalışanına içe aktararak ve ana iş parçacığında gösterilecek bir işlev kümesi tanımlayarak ayarlarsınız. Ardından ana iş parçacığında Comlink'i içe aktarır, çalışanı sarmalar ve açık işlevlere erişim elde edersiniz:

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şlevin, değerin kendisi yerine bir değer için sözü döndürmesi gerekir.

Web çalışanına hangi kodu 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 bir erişime dayalı parçalarını bir çalışana yerleştiremezsiniz. Yine de bir çalışana taşınan her küçük kod, 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 de ç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 doğası gereği DOM'ye bağlıdır. Bu durum, OMT mimarisine geçiş yapmayı zorlaştıracak gibi görünüyor.

Bununla birlikte, kullanıcı arayüzü sorunlarının eyalet yönetimi gibi diğer konulardan ayrıldığı bir modele geçersek web çalışanları çerçeve tabanlı uygulamalarda bile son derece yararlı olabilir. PROXX için uyguladığı yaklaşım tam olarak budur.

PROXX: OMT örnek olayı

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

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

  • Animasyonların ve geçişlerin oluşturulması, ana iş parçacığı tarafından işlenir.
  • Bir web çalışanı, tamamen hesaplamaya dayalı oyun mantığıyla ilgileniyor.

OMT, PROXX'in özellikli telefon performansında ilginç etkiler yarattı. OMT olmayan sürümde, kullanıcı arayüzü kullanıcı etkileşimde bulunduktan sonra altı saniye boyunca dondurulur. Geri bildirim yoktur ve kullanıcının başka bir şey 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üncellemesini tamamlaması on iki saniye sürüyor. Bu bir performans kaybı gibi görünse de, aslında kullanıcının verdiği geri bildirimin artmasına yol açar. Yavaşlama, uygulamanın hiçbir kare göndermeyen, OMT olmayan sürümden daha fazla kare gönderiyor olmasından kaynaklanır. Böylece kullanıcı bir şeylerin olduğunu bilir ve kullanıcı arayüzü güncellendikçe oynamaya devam edebilir. Böylece oyun çok daha iyi hale gelir.

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

Bu, bilinçli bir şekilde verilmesidir: Kısıtlı cihaz kullanıcılarına, üst segment cihaz kullanıcılarını cezalandırmadan daha iyi hissettiren 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ı daha hızlı hale getirmez:

  • İşi azaltmıyor, yalnızca işi ana iş parçacığından taşıyorsunuz.
  • Web çalışanı ile ana iş parçacığı arasındaki ek iletişim ek yükü bazen işleri marjinal şekilde yavaşlatabilir.

Dengeleri göz önünde bulundurmak

Ana iş parçacığı JavaScript çalışırken kaydırma gibi kullanıcı etkileşimlerini işlemekte serbest olduğundan, toplam bekleme süresi marjinal şekilde daha uzun olsa da atlanan kare sayısı daha azdır. Silinen kareler söz konusu olduğunda hata payı daha az olduğundan, kullanıcının biraz beklemesi tercih edilir. Bunun nedeni, kullanıcının bekleme süresini algılamadan önce yüzlerce milisaniyelik bir sürede kare atılması gerçekleşir.

Cihazlar arasında performansın tahmin edilememesi nedeniyle, OMT mimarisinin amacı aslında paralelleştirmenin performans avantajlarıyla değil, riski azaltmak, yani uygulamanızı son derece değişken çalışma zamanı koşullarında daha sağlam hale getirmekle ilgilidir. Dayanıklılıktaki artış ve kullanıcı deneyiminde yapılan iyileştirmeler, hızdan ödün verilmesine değer.

Araçlar hakkında bir not

Web çalışanları henüz popüler olmadığından web paketi ve Birleştirme gibi çoğu modül aracı, bunları kullanıma hazır olarak desteklemez. (Ancak Parsel öyledir!) Neyse ki, web çalışanlarının webpack ve Rollup ile çalışmasını sağlayan eklentiler var:

Özet

Özellikle gitgide küreselleşen bir pazarda uygulamalarımızın mümkün olduğunca güvenilir ve erişilebilir olduğundan emin olmak için kısıtlı cihazları desteklememiz gerekiyor. Çoğu kullanıcı web'e global olarak bu şekilde erişiyor. OMT, bu tür cihazlardaki performansı, ileri teknoloji cihazların kullanıcılarını olumsuz etkilemeden artırmak için umut verici bir yol sunuyor.

OMT'nin ikincil avantajları da vardır:

Web çalışanlarının korkutması gerekmez. Comlink gibi araçlar, çalışanların iş yükünü azaltıyor ve onları çok çeşitli web uygulamaları için uygun bir seçenek haline getiriyor.

Unsplash'ten James Peacock'ın lokomotif resmi.