Web Worker, bir JavaScript dosyasını sayfanın ana iş parçacığından ayrı bir arka plan iş parçacığında çalıştırmanızı sağlayan tarayıcı API'sidir. new Worker("worker.js") ile oluşturulur, ana sayfayla yalnızca postMessage() ve message olayı üzerinden konuşur ve DOM'a doğrudan erişemez. Uzun süren hesaplamaları worker'a taşıdığınızda kullanıcı arayüzü donmadan tepki vermeye devam eder.

Bu yazıda bir worker'ı oluşturmayı, iki yönlü mesajlaşmayı, type: "module" seçeneğini, verinin structured clone ile nasıl kopyalandığını, transferable nesnelerle sahipliğin nasıl devredildiğini, hata olaylarını ve shared worker farkını MDN belgelerine dayanarak anlatıyoruz. Tarayıcıdan bağımsız kısımları (structured clone ve aktarım) Node.js 24 ile çalıştırıp çıktılarını gösterdik. Tüm Web API konularının haritası için Web API öğrenme rehberine bakabilirsiniz.

Web Worker nedir, neden ayrı bir iş parçacığı?

Bir web sayfasındaki JavaScript normalde tek bir ana iş parçacığında çalışır. Arayüzün çizilmesi, tıklama gibi kullanıcı olaylarının işlenmesi ve sizin kodunuz aynı sırayı paylaşır. Büyük bir JSON dosyasını işlemek, bir görüntünün piksellerini tek tek dolaşmak ya da karmaşık bir hesap yapmak bu sırayı uzun süre işgal ederse sayfa donmuş gibi görünür. Olay döngüsünün bu davranışını JavaScript event loop yazısında ayrıntılı bulabilirsiniz.

Worker, Worker() kurucusuna verdiğiniz adresteki betiği gerçek bir işletim sistemi iş parçacığında çalıştırır. Bu betiğin global bağlamı window değildir: dedicated (adanmış) worker için DedicatedWorkerGlobalScope, shared worker için SharedWorkerGlobalScope nesnesidir. Bu yüzden worker içinde globale ulaşmak için window yerine self kullanılır; window yazmak hataya yol açar.

Worker içinde DOM'u değiştiremezsiniz, window nesnesinin bazı yöntemleri de yoktur. Buna karşın fetch(), XMLHttpRequest (ancak responseXML her zaman null döner), WebSocket, IndexedDB, zamanlayıcılar, structuredClone() ve queueMicrotask() gibi pek çok araç kullanılabilir. MDN'deki "Functions and classes available to workers" listesi bunların tamamını verir; importScripts() ise yalnızca worker'larda bulunan bir fonksiyondur.

İlk worker: oluşturma ve iki yönlü mesajlaşma

MDN'in temel örneği iki sayıyı worker'a gönderip çarpımını geri alır. Önce destek kontrolü yapılır, sonra worker oluşturulur. Kurucuya verilen adres mevcut HTML sayfasının konumuna göre çözülür ve çağıran belgeyle aynı origin'de olmalıdır; aksi hâlde yalnızca blob: ya da data: adresi kabul edilir. Aynı origin kavramını CORS yazısında anlattık.

html
<label>Birinci sayı <input type="number" id="first"></label>
<label>İkinci sayı <input type="number" id="second"></label>
<p class="result"></p>
<script src="main.js"></script>
javascript
// main.js
const first = document.querySelector("#first");
const second = document.querySelector("#second");
const result = document.querySelector(".result");

if (window.Worker) {
  const myWorker = new Worker("worker.js");

  [first, second].forEach((input) => {
    input.onchange = () => {
      myWorker.postMessage([first.value, second.value]);
    };
  });

  myWorker.onmessage = (e) => {
    result.textContent = e.data;
  };
}
javascript
// worker.js
onmessage = (e) => {
  const workerResult = `Sonuç: ${e.data[0] * e.data[1]}`;
  postMessage(workerResult);
};

Dikkat edilecek ayrıntı şudur: ana betikte onmessage ve postMessage() worker nesnesinin üzerinden çağrılır (myWorker.postMessage), worker içinde ise doğrudan yazılır, çünkü worker'ın kendisi global kapsamdır. Mesajın içeriği her iki tarafta da olay nesnesinin data özelliğinde gelir. postMessage() tek seferde tek bir değer gönderir; birden çok değer için örnekteki gibi bir dizi ya da nesne kullanın. Gönderilecek veri önemsizse bile ilk parametre zorunludur, bu durumda açıkça null ya da undefined verilmelidir.

Webpack, Vite ve Parcel gibi paketleyiciler worker adresini new URL("worker.js", import.meta.url) biçiminde vermenizi önerir. Böylece yol HTML sayfasına değil mevcut betiğe göre çözülür ve paketleyici dosyayı güvenle yeniden adlandırabilir.

Worker() seçenekleri: type, name ve credentials

Worker() seçenekleri: type, name ve credentials: Oluşturma, Bağımlılık yükleme, credentials seçeneği, Betik adresi

new Worker(url, options) çağrısındaki seçenek nesnesi üç özellik alır. type değeri "classic" (varsayılan) ya da "module" olabilir. Module worker bir ECMAScript modülü olarak yüklenir ve içinde import deyimini kullanabilirsiniz; modül sözdizimi için JavaScript öğrenme rehberindeki modül bölümüne bakabilirsiniz. name worker'ın global kapsamına bir ad verir ve çoğunlukla hata ayıklamada işe yarar. credentials ise module worker modül içe aktarırken kimlik bilgisi gönderilip gönderilmeyeceğini belirler; değerleri omit, same-origin (varsayılan) ve include olup classic worker'da yok sayılır.

javascript
const klasik = new Worker("worker_classic.js");
const modul = new Worker("worker_module.js", {
  type: "module",
  name: "rapor-hesaplayici",
});

Classic worker'da dış betik yüklemenin yolu importScripts() fonksiyonudur. Sıfır ya da daha fazla adres alır, başka origin'lerden betik de yükleyebilir. Betikler herhangi bir sırayla indirilebilir ama verdiğiniz sırayla ve senkron olarak çalıştırılır: fonksiyon, tüm betikler yüklenip çalışmadan geri dönmez. Bir betik yüklenemezse NETWORK_ERROR fırlatılır ve sonraki kod çalışmaz.

ÖzellikClassic workerModule worker
Oluşturmanew Worker(url) veya type: classicnew Worker(url, { type: "module" })
Bağımlılık yüklemeimportScripts() ile senkronimport deyimi ile
credentials seçeneğiYok sayılıromit, same-origin (varsayılan), include
Betik adresiAynı origin, blob: veya data:Aynı origin, blob: veya data:

Veri nasıl taşınır: structured clone algoritması

Veri nasıl taşınır: structured clone algoritması

Ana sayfa ile worker arasında giden veri paylaşılmaz, kopyalanır. Gönderilen nesne bir uçta serileştirilir, diğer uçta yeniden oluşturulur; iki taraf aynı nesneye değil birbirinin kopyasına sahip olur. Bu işi structured clone algoritması yapar. Aynı algoritma structuredClone() fonksiyonunda, IndexedDB'ye nesne kaydederken ve window.postMessage() çağrısında da kullanılır.

Algoritma JSON'un taşıyamadığı bazı şeyleri taşıyabilir: döngüsel başvurular, Date, Map, Set, RegExp, ArrayBuffer, typed array'ler, Blob, File ve belirli hata türleri desteklenir. Aşağıdaki örneği Node.js 24 ile çalıştırdık; structuredClone() tarayıcıda ve Node'da aynı standarda göre çalışır.

javascript
const kayit = { ad: "Ayşe", tarih: new Date(0), etiketler: new Set(["a", "b"]) };
kayit.kendisi = kayit; // döngüsel başvuru
const kopya = structuredClone(kayit);
console.log(kopya === kayit); // false
console.log(kopya.kendisi === kopya); // true
console.log(kopya.tarih instanceof Date); // true
console.log(kopya.tarih.toISOString()); // 1970-01-01T00:00:00.000Z
console.log(kopya.etiketler.has("b")); // true

Bazı şeyler ise kopyalanamaz. Function nesneleri ve DOM düğümleri DataCloneError adlı bir DOMException fırlatır. Prototip zinciri kopyalanmaz, bu yüzden bir sınıf örneği karşı tarafa düz nesne olarak gider ve yöntemlerini kaybeder. Getter, setter ve özellik tanımlayıcıları korunmaz, sınıfların özel (#) alanları kopyalanmaz, RegExp nesnesinin lastIndex değeri de taşınmaz.

javascript
try {
  structuredClone({ hesapla: () => 42 });
} catch (err) {
  console.log(err.name); // DataCloneError
  console.log(err instanceof DOMException); // true
}

class Kullanici {
  #gizli = "x";
  constructor(ad) { this.ad = ad; }
  selam() { return `Merhaba ${this.ad}`; }
}
const k = structuredClone(new Kullanici("Can"));
console.log(k instanceof Kullanici); // false
console.log(typeof k.selam); // undefined
console.log(JSON.stringify(k)); // {"ad":"Can"}

Sahipliği devretmek: transferable nesneler

Büyük bir ikili veriyi kopyalamak hem zaman hem bellek harcar. Transferable nesneler bu sorunu çözer: ArrayBuffer gibi bir nesneyi aktarım listesine koyduğunuzda alttaki bellek kopyalanmadan karşı tarafa taşınır. Gönderen taraftaki nesne artık o kaynağa sahip değildir; MDN bunu "detached" (ayrılmış) olarak tanımlar ve nesneyi okumaya ya da yazmaya çalışmak hata fırlatır, byteLength ise 0 olur.

postMessage(message, transfer) ya da postMessage(message, { transfer }) biçimleri kullanılır. Önemli bir ayrıntı: aktarım listesi kaynağın nasıl gönderileceğini söyler, kaynağı kendisi göndermez. Aktarılan nesne mesajın içinde de yer almalıdır, yoksa karşı tarafta erişilemez ama gönderen tarafta yine de kullanılamaz hâle gelir. Aşağıda aynı mekanizmayı MessageChannel portlarıyla gösteriyoruz; worker'ın postMessage() yöntemi de iç tarafta MessagePort yöntemine devreder.

javascript
const { port1, port2 } = new MessageChannel();

port2.onmessage = (event) => {
  console.log("alınan bayt:", event.data.byteLength); // alınan bayt: 8
  port1.close();
};

const tampon = new ArrayBuffer(8);
console.log("önce:", tampon.byteLength); // önce: 8
port1.postMessage(tampon, [tampon]);
console.log("sonra:", tampon.byteLength); // sonra: 0

Çıktı sırası öğreticidir: "sonra: 0" satırı hemen basılır, çünkü aktarım çağrı anında gerçekleşir. Mesaj ise kuyruğa eklenir ve olay döngüsünün sonraki turunda alınır, bu yüzden "alınan bayt: 8" en son görünür.

Typed array'ler (Uint8Array, Int32Array ve benzerleri) serileştirilebilir ama aktarılamaz; aktarılabilen, alttaki buffer özelliğidir. Aynı kural structuredClone(deger, { transfer }) için de geçerlidir:

javascript
const dizi = new Uint8Array([1, 2, 3, 4]);
const tasinan = structuredClone(dizi, { transfer: [dizi.buffer] });
console.log(tasinan[3]); // 4
console.log(dizi.byteLength); // 0

const baska = new Uint8Array(4);
try {
  structuredClone(baska, { transfer: [baska] });
} catch (err) {
  console.log(err.name); // DataCloneError
}

Hata yönetimi ve worker'ı sonlandırma

Worker içinde çalışma zamanı hatası oluştuğunda ana taraftaki Worker nesnesinde error olayı tetiklenir. Olay ErrorEvent arayüzünü uygular, kabarcıklanmaz ve iptal edilebilir; varsayılan davranışı durdurmak için preventDefault() çağrılabilir. İşinize yarayacak üç alan vardır: okunabilir hata metni message, hatanın oluştuğu dosya filename ve satır numarası lineno.

javascript
myWorker.addEventListener("error", (event) => {
  console.error(`${event.filename}:${event.lineno} ${event.message}`);
});

myWorker.addEventListener("messageerror", (event) => {
  // Gelen mesaj seri durumdan geri çevrilemedi
});

// İş bittiğinde ya da kullanıcı iptal ettiğinde
myWorker.terminate();

messageerror olayı ise worker'dan gelen bir mesaj seri durumdan geri çevrilemediğinde tetiklenir; iptal edilemez ve kabarcıklanmaz. terminate() çağrısı worker iş parçacığını hemen öldürür, worker'ın elindeki işi bitirmesini beklemez. Kurucu da hata fırlatabilir: betiğin MIME türü yanlışsa NetworkError, belge worker başlatamıyorsa ya da aynı origin kuralı çiğneniyorsa SecurityError, adres ayrıştırılamıyorsa SyntaxError. Bu DOMException adlarının tamamını Web API hata adları yazısında topladık.

Worker'lar alt worker da başlatabilir. Alt worker aynı origin'de olmalıdır ve adresi sayfaya göre değil üst worker'ın konumuna göre çözülür. Bu, bağımlılıkları worker dosyalarının yanında tutmayı kolaylaştırır.

Shared worker: birden çok sayfanın ortak worker'ı

Dedicated worker yalnızca onu oluşturan betik tarafından kullanılabilir. SharedWorker ise farklı pencereler, iframe'ler ve hatta başka worker'lar tarafından paylaşılabilir; bunun için hepsi tam olarak aynı origin'de (protokol, alan adı ve port) olmalıdır. İletişim örtük değil açık bir port nesnesi üzerinden yapılır.

javascript
// sayfa tarafı
const ortak = new SharedWorker("worker.js");
orthak.port.onmessage = (e) => {
  result.textContent = e.data;
};
orthak.port.postMessage([3, 4]);
javascript
// worker.js
onconnect = (e) => {
  const port = e.ports[0];
  port.onmessage = (e) => {
    port.postMessage(`Sonuç: ${e.data[0] * e.data[1]}`);
  };
};

Port bağlantısı ya onmessage atanarak örtük olarak ya da start() çağrılarak açıkça başlatılır. start() yalnızca message olayını addEventListener() ile dinlediğinizde gerekir; iki yönlü iletişimde her iki tarafta da çağrılmalıdır. Shared worker, ona başvuran hiçbir pencere, iframe ya da worker kalmadığında kapatılır. MDN, kurucuya verilen extendedLifetime seçeneğinin tüm başvurular kapandıktan sonra worker'ı kısa bir süre daha yaşatabildiğini de anlatır; bu seçeneğin tarayıcı desteği için MDN uyumluluk tablosuna bakın.

Güvenlik: CSP ve worker adresi

Worker kendi yürütme bağlamına sahiptir ve genel olarak onu oluşturan belgenin Content Security Policy kurallarına tabi değildir. Örneğin belge script-src 'self' ile eval() kullanımını engellese bile worker içinde eval() çalışabilir. Worker'a CSP uygulamak için worker betiğini sunan yanıta ayrı bir Content-Security-Policy başlığı eklemeniz gerekir. İstisna, adresi blob: ya da data: olan worker'lardır: bunlar oluşturan belgenin CSP'sini devralır.

MDN, Worker() kurucusuna verilen adresi bir "injection sink" olarak işaretler: kullanıcıdan gelen bir adresi doğrudan worker'a vermek XSS'e kapı açar. Betik kaynaklarını CSP ile sınırlamak ve Trusted Types uygulandığında dize yerine TrustedScriptURL vermek önerilir. Genel XSS ve CSP konusu için XSS ve CSP koruma yazısını okuyabilirsiniz.

Worker türlerini karşılaştırma

Worker türlerini karşılaştırma: Dedicated worker, Shared worker, Service worker
TürKim kullanırİletişimTipik iş
Dedicated workerYalnızca onu oluşturan betikworker.postMessage ve onmessageAğır hesaplama, veri işleme
Shared workerAynı origin'deki birden çok pencere ve iframeworker.port üzerindenSekmeler arası ortak durum
Service workerKapsamındaki sayfalarOlaylar (fetch, push) ve mesajlarAğ istekleri, çevrimdışı çalışma

Service worker da bir worker türüdür ama amacı farklıdır: sayfa ile ağ arasında vekil sunucu gibi durur. Kaydını ve yaşam döngüsünü service worker yazısında anlatıyoruz.

Sık yapılan hatalar

  • Worker içinde document ya da window kullanmaya çalışmak. DOM işini ana tarafa mesajla bırakın, global için self yazın.
  • Fonksiyon, DOM düğümü ya da sınıf örneği göndermek. Fonksiyon ve DOM düğümü DataCloneError verir, sınıf örneği yöntemlerini kaybeder.
  • Aktarılan ArrayBuffer nesnesini gönderdikten sonra kullanmaya devam etmek. Aktarımdan sonra byteLength 0 olur ve okuma ya da yazma hata fırlatır.
  • Typed array'in kendisini aktarım listesine koymak. Listeye dizi.buffer eklenmelidir.
  • Worker dosyasını başka bir alan adından yüklemeye çalışmak. Kurucu aynı origin ister; gerekiyorsa aynı origin'deki bir ara worker ya da blob kullanılır.
  • Shared worker portunu addEventListener() ile dinleyip port.start() çağırmayı unutmak.
  • Belgenin CSP'sinin worker'ı da koruduğunu varsaymak. Worker betiğine ayrı başlık gerekir.

Sık Sorulan Sorular

Worker içinde fetch kullanabilir miyim?

Evet. fetch() ve XMLHttpRequest worker'larda kullanılabilir. Tek istisna, XHR'nin responseXML özelliğinin worker'da her zaman null olmasıdır. Ağ istekleri için Fetch API yazısına bakabilirsiniz.

Worker'a gönderdiğim nesneyi değiştirirsem karşı taraf görür mü?

Hayır. Veri kopyalanır; iki uçta ayrı nesneler vardır. Değişikliği karşı tarafa bildirmek için yeni bir mesaj göndermeniz gerekir. Aynı belleği paylaşmanın tek yolu SharedArrayBuffer kullanmaktır ve bu, çapraz origin izolasyonu gerektirir.

Worker'ı nasıl durdururum?

Ana taraftan worker.terminate() çağırırsınız; iş parçacığı hemen sonlandırılır. Yarım kalan iş tamamlanmaz, bu yüzden önemli bir sonucu kaybetmek istemiyorsanız önce worker'dan bir "bitti" mesajı bekleyin.

Worker'da localStorage kullanılabilir mi?

MDN'in worker'larda kullanılabilen API listesinde IndexedDB, Fetch ve WebSockets gibi API'ler yer alır, Web Storage ise bu listede yoktur. Service worker belgesi de localStorage senkron çalıştığı için service worker içinde kullanılamayacağını açıkça yazar. Worker'da kalıcı veri için IndexedDB tercih edin, sayfada basit anahtar değer saklama için localStorage yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Web API öğrenme rehberi · Service worker kaydı ve yaşam döngüsü · postMessage, MessageChannel ve BroadcastChannel · DOMException ve güvenli bağlam hataları · JavaScript event loop