Node.js'te bir işlemi iptal etmenin standart yolu AbortController'dır: bir denetleyici oluşturursunuz, onun signal özelliğini iptal edilebilir API'ye { signal } seçeneğiyle verirsiniz ve vazgeçtiğinizde controller.abort() çağırırsınız. İşlem AbortError ile sonlanır. Zaman aşımı için denetleyiciye bile gerek yoktur: AbortSignal.timeout(5000) beş saniye sonra kendiliğinden iptal olan bir sinyal üretir; AbortSignal.any([...]) ise birkaç sinyali tek sinyalde birleştirir.

Bu yazıda AbortController ve AbortSignal API'sini, iptal sebebini (reason), fs, timers/promises, events ve fetch ile iptal örneklerini, iptal hatalarını doğru tanımayı ve kendi iptal edilebilir fonksiyonunuzu yazmayı Node.js v26.13.0 ile çalıştırılmış örneklerle anlatıyoruz. Serinin diğer yazıları için Node.js öğrenme rehberi sayfasına bakabilirsiniz.

AbortController ve AbortSignal nedir?

AbortController ve AbortSignal nedir?

İkisi de globaldir, içe aktarma gerektirmez. Belgelere göre AbortController, seçili Promise tabanlı API'lerde iptal sinyali vermek için kullanılan yardımcı bir sınıftır ve web platformundaki AbortController API'sine dayanır; v15.4.0'da deneysel olmaktan çıkmıştır. Her denetleyicinin bir signal özelliği vardır. Bu AbortSignal nesnesi bir EventTarget'tır: iptal edildiğinde bir kez 'abort' olayı yayar, aborted özelliği true olur ve reason özelliği iptal sebebini taşır.

Görev ayrımı önemlidir: iptal etme yetkisi denetleyicide, iptali izleme yetkisi sinyaldedir. Bir fonksiyona yalnızca signal'ı verirseniz o fonksiyon iptal olup olmadığını öğrenebilir ama kendisi iptal edemez.

javascript
const ac = new AbortController();
const { signal } = ac;

signal.addEventListener('abort', (e) => {
  console.log('olay türü:', e.type);
}, { once: true });

console.log('önce:', signal.aborted, signal.reason);
ac.abort(new Error('kullanıcı vazgeçti'));
console.log('sonra:', signal.aborted, signal.reason.message);
ac.abort('ikinci kez');
console.log('reason değişmedi:', signal.reason.message);
// çıktı: önce: false undefined
// çıktı: olay türü: abort
// çıktı: sonra: true kullanıcı vazgeçti
// çıktı: reason değişmedi: kullanıcı vazgeçti

Belgeler, bir denetleyicinin 'abort' olayını yalnızca bir kez tetikleyeceğini belirtir; ikinci abort() çağrısı ne yeni bir olay üretti ne de sebebi değiştirdi. Belgelerin iki önerisi de bu örnekte uygulandı: dinleyiciyi eklemeden önce signal.aborted değerine bakmak ve dinleyiciyi { once: true } ile eklemek. İkincisi yapılmazsa dinleyici sinyalle birlikte bellekte kalabilir; belgeler bunun bellek sızıntısına yol açabileceği konusunda uyarır.

İptal sebebi: reason, AbortError ve TimeoutError

abort(reason)'a verdiğiniz değer, signal.reason olarak saklanır ve iptal edilen API'lerin hatasında kullanılabilir. Sebep vermezseniz ne olur? AbortSignal.abort() zaten iptal edilmiş bir sinyal döndürür, AbortSignal.timeout(ms) ise belirtilen süre sonunda iptal olur. Çıktı, varsayılan sebeplerin birer DOMException olduğunu gösteriyor:

javascript
const a = AbortSignal.abort();
console.log(a.aborted, a.reason.name, a.reason.code, a.reason instanceof DOMException);

const t = AbortSignal.timeout(10);
await new Promise((r) => setTimeout(r, 30));
console.log(t.aborted, t.reason.name, t.reason.message);
// çıktı: true AbortError 20 true
// çıktı: true TimeoutError The operation was aborted due to timeout

20 sayısı, Errors belgesinde anlatılan eski Web IDL hata kodudur ve ABORT_ERR adına karşılık gelir. Zaman aşımında ise sebebin adı TimeoutError'dır. Bu ayrım, kullanıcının iptal ettiği bir işlemle süresi dolan bir işlemi ayırt etmenizi sağlar.

throwIfAborted(): uzun döngülerde kontrol noktası

signal.throwIfAborted(), sinyal iptal edilmişse signal.reason değerini fırlatır, edilmemişse hiçbir şey yapmaz. Kendi yazdığınız, birden çok adımdan oluşan bir işte her adımın başına koymak, iptali hızlıca fark etmenin en kısa yoludur. Sinyal isteğe bağlıysa signal?.throwIfAborted() biçiminde yazabilirsiniz; optional chaining için optional chaining yazısına bakın.

javascript
function satirlariIsle(satirlar, signal) {
  let sayi = 0;
  for (const s of satirlar) {
    signal?.throwIfAborted();
    sayi += s.length;
  }
  return sayi;
}

const ac = new AbortController();
console.log(satirlariIsle(['ab', 'cde'], ac.signal));
ac.abort(new Error('iptal edildi'));
try {
  satirlariIsle(['ab', 'cde'], ac.signal);
} catch (err) {
  console.log('yakalandı:', err.message);
}
// çıktı: 5
// çıktı: yakalandı: iptal edildi

Zaman aşımı: AbortSignal.timeout()

AbortSignal.timeout(delay), delay milisaniye sonra iptal olacak yeni bir sinyal döndürür. Denetleyici oluşturup setTimeout ile abort() çağırmanın kısa yoludur. Aşağıda timers/promises ile kullanıldığında hatanın kendisi AbortError, cause özelliği ise asıl sebep olan TimeoutError:

javascript
import { setTimeout as bekle } from 'node:timers/promises';

try {
  await bekle(1000, null, { signal: AbortSignal.timeout(30) });
} catch (err) {
  console.log(err.name, err.code);
  console.log('cause:', err.cause?.name);
}
// çıktı: AbortError ABORT_ERR
// çıktı: cause: TimeoutError

Bir davranışa da dikkat edin: Node.js v26.13.0 ile denediğimizde AbortSignal.timeout()'un kendi zamanlayıcısı süreci açık tutmadı. Üst düzeyde yalnızca böyle bir sinyalle bekleyen bir await once(ee, 'hazir', { signal: AbortSignal.timeout(20) }) yazdığımızda, süre dolmadan süreç "unsettled top-level await" uyarısı ve 13 çıkış koduyla kapandı. Process belgelerine göre 13, üst düzeyde beklenen bir Promise'in hiç sonuçlanmadığı anlamına gelir. Gerçek uygulamalarda bekleyen bir soket ya da zamanlayıcı süreci zaten açık tuttuğu için bu genellikle görünmez, ama kısa betiklerde şaşırtabilir. Çıkış kodları için process.exit ve çıkış kodları yazısına bakabilirsiniz.

Birden çok sebep: AbortSignal.any()

Çoğu zaman iki iptal sebebiniz vardır: kullanıcının vazgeçmesi ve zaman aşımı. AbortSignal.any(signals), verilen sinyallerden herhangi biri iptal olduğunda iptal olan yeni bir sinyal döndürür ve reason değerini iptale yol açan sinyalden alır. YAML kaydına göre v20.3.0 ve v18.17.0'da eklenmiştir.

javascript
import { setTimeout as bekle } from 'node:timers/promises';

const kullanici = new AbortController();
const birlesik = AbortSignal.any([kullanici.signal, AbortSignal.timeout(1000)]);

setTimeout(() => kullanici.abort(new Error('iptal butonu')), 20);
try {
  await bekle(5000, null, { signal: birlesik });
} catch (err) {
  console.log(err.name, '| sebep:', birlesik.reason.message);
}
// çıktı: AbortError | sebep: iptal butonu

Node.js API'leriyle iptal örnekleri

Node.js API'leriyle iptal örnekleri: Dosya okuma ve yazma, Olay beklemek, fetch ile HTTP isteğine zaman aşımı, signal kabul eden diğer API'ler

Dosya okuma ve yazma

fs.readFile, fs.writeFile ve Promise sürümleri signal seçeneğini kabul eder. Belgelere göre iptal edilen readFile Promise'i AbortError ile reddedilir. Önemli bir sınır da belgelenmiştir: iptal, işletim sistemine giden tek tek istekleri durdurmaz; readFile'ın kendi iç arabelleğe alma işini durdurur. Dosya okuma ayrıntıları için readFile yazısına bakın.

javascript
import { readFile, writeFile } from 'node:fs/promises';

await writeFile('buyuk.txt', 'x'.repeat(1_000_000));
const ac = new AbortController();
const okuma = readFile('buyuk.txt', { signal: ac.signal });
ac.abort();
try {
  await okuma;
} catch (err) {
  console.log(err.name, err.code);
}
// çıktı: AbortError ABORT_ERR

Olay beklemek

events.once() ve events.on() da signal alır; böylece hiç gelmeyebilecek bir olayı sonsuza kadar beklemezsiniz. Ayrıntılar EventEmitter yazısında. Aşağıdaki örnek aynı zamanda iki farklı hata türünü yan yana gösteriyor:

javascript
import { once, EventEmitter } from 'node:events';

const ee = new EventEmitter();
const ac = new AbortController();
setTimeout(() => ac.abort(), 20);
try {
  await once(ee, 'hazir', { signal: ac.signal });
} catch (err) {
  console.log('events.once:', err.name, err.code, err instanceof DOMException);
}
try {
  AbortSignal.abort().throwIfAborted();
} catch (err) {
  console.log('throwIfAborted:', err.name, err.code, err instanceof DOMException);
}
// çıktı: events.once: AbortError ABORT_ERR false
// çıktı: throwIfAborted: AbortError 20 true

fetch ile HTTP isteğine zaman aşımı

Global fetch() da signal seçeneğini destekler. Aşağıdaki betik 500 ms'de yanıt veren yerel bir sunucu açıyor, isteği 50 ms'lik bir zaman aşımıyla gönderiyor ve sonunda sunucuyu kapatıyor:

javascript
import http from 'node:http';

const sunucu = http.createServer((req, res) => {
  setTimeout(() => res.end('geç cevap'), 500);
});
await new Promise((r) => sunucu.listen(0, r));
const { port } = sunucu.address();

try {
  await fetch(`http://localhost:${port}/`, { signal: AbortSignal.timeout(50) });
} catch (err) {
  console.log(err.name, '|', err.message);
}
sunucu.closeAllConnections();
sunucu.close();
// çıktı: TimeoutError | The operation was aborted due to timeout

Burada hatanın adı doğrudan TimeoutError; çünkü fetch bir web platformu API'sidir ve sinyalin sebebini olduğu gibi kullanır. fetch kullanımının geri kalanı için fetch ile HTTP isteği yazısına, HTTP sunucusu için node:http yazısına bakabilirsiniz.

signal kabul eden diğer API'ler

Bu yazıda kullanılanların dışında da birçok Node.js API'si signal seçeneği sunar. Okuduğumuz belgelerde geçenlerden bazıları: fs.watch() ve fsPromises.watch() (sinyal iptal edilince izleyici kapanır, bkz. fs.watch yazısı), fs.writeFile() ve fsPromises.writeFile(), events.on() ile olay akışı ve timers/promises içindeki setInterval iterator'ı. stream.addAbortSignal() ile bir stream'e sinyal bağlamak ve server.listen()'e signal vermek de mümkündür; ancak bunlar uzun ömürlü kaynakları kapatmaya yöneliktir ve aşağıda anlatılan DEP0209 kapsamına girer. Bir API'nin sinyal destekleyip desteklemediğini her zaman o modülün belgesindeki seçenekler listesinden kontrol edin.

İptal hatasını doğru tanımak

İptal hatasını doğru tanımak: events.once / fs / timers iptali, timers/promises + AbortSignal.timeout, signal.throwIfAborted() (varsayılan sebep), fetch + AbortSignal.timeout

Errors belgesi iki tür iptal hatası olduğunu açıklar. Web platformu API'leri (fetch(), AbortController, AbortSignal, Web Streams) bir DOMException fırlatır; bunun code özelliği sayıdır (AbortError için 20). events.once() gibi Node.js'e özgü API'ler ise name değeri 'AbortError', code değeri 'ABORT_ERR' olan normal bir Error fırlatır. Belgelere göre her iki durumda da iptal hatasını tanımak için err?.name === 'AbortError' kontrolü yeterlidir. Ancak yukarıdaki fetch örneğinde gördüğünüz gibi, zaman aşımında hata adı TimeoutError olabilir; ikisini birlikte kontrol etmek daha güvenlidir.

Durumerr.nameerr.codeSınıf
events.once / fs / timers iptaliAbortErrorABORT_ERRNode.js Error
timers/promises + AbortSignal.timeoutAbortError (cause: TimeoutError)ABORT_ERRNode.js Error
signal.throwIfAborted() (varsayılan sebep)AbortError20DOMException
fetch + AbortSignal.timeoutTimeoutError23 (DOMException kodu)DOMException
abort(ozelHata) verildiyseKendi hatanızın adıKendi hatanızın koduSizin verdiğiniz nesne
javascript
function iptalMi(err) {
  return err?.name === 'AbortError' || err?.name === 'TimeoutError';
}

Kendi iptal edilebilir fonksiyonunuzu yazmak

Bir API'yi signal destekli hâle getirmek için üç kuralı uygulayın: işe başlamadan önce sinyalin zaten iptal edilmiş olup olmadığına bakın, 'abort' dinleyicisini { once: true } ile ekleyin ve iş normal biçimde bittiğinde dinleyiciyi kaldırın. Reddederken signal.reason kullanmak, çağıranın verdiği sebebi korur.

javascript
function bekleIptalli(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) return reject(signal.reason);
    const zamanlayici = setTimeout(() => {
      signal?.removeEventListener('abort', iptal);
      resolve('bitti');
    }, ms);
    function iptal() {
      clearTimeout(zamanlayici);
      reject(signal.reason);
    }
    signal?.addEventListener('abort', iptal, { once: true });
  });
}

console.log(await bekleIptalli(10));
const ac = new AbortController();
setTimeout(() => ac.abort(new Error('zaman doldu')), 10);
await bekleIptalli(1000, ac.signal).catch((e) => console.log('ret:', e.message));
await bekleIptalli(10, AbortSignal.abort('önceden iptal')).catch((e) => console.log('ret:', e));
// çıktı: bitti
// çıktı: ret: zaman doldu
// çıktı: ret: önceden iptal

Belgeler, 'abort' olayını doğrudan dinlemenin güvenli olmadığını da not eder: sinyale erişimi olan başka bir kod stopImmediatePropagation() çağırarak sizin dinleyicinizin çalışmasını engelleyebilir; ayrıca dinleyiciyi kaldırmayı unutmak kolaydır. node:events modülündeki addAbortListener(signal, listener) bu iki sorunu çözer ve kaldırmayı kolaylaştıran bir Disposable döndürür. YAML kaydına göre v24.0.0 ve v22.16.0'da kararlı (Stable) hâle geldi; v26.11.0'da ise sinyal zaten iptal edilmişse dinleyicinin bir mikro görevde abort olayıyla çağrılması ve dispose edilmesinin bekleyen çağrıyı iptal etmesi davranışı eklendi.

javascript
import { addAbortListener } from 'node:events';

const ac = new AbortController();
ac.signal.addEventListener('abort', (e) => e.stopImmediatePropagation());
ac.signal.addEventListener('abort', () => console.log('normal dinleyici'));

addAbortListener(ac.signal, () => console.log('addAbortListener yine de çalıştı'));
{
  using _ = addAbortListener(ac.signal, () => console.log('bu yazılmaz'));
}
ac.abort();
// çıktı: addAbortListener yine de çalıştı

İlk dinleyici yayılımı durdurduğu için normal dinleyici hiç yazılmadı, addAbortListener ile eklenen ise yine çalıştı. using bloğu bittiğinde ikinci dinleyici otomatik kaldırıldığı için o da çalışmadı.

İlgili yardımcılar ve kullanımdan kaldırılan desen

  • util.aborted(signal, resource): Sinyal iptal edildiğinde çözülen bir Promise döndürür. resource nesnesine zayıf referans tutar; nesne sinyal iptal olmadan çöp toplanırsa Promise beklemede kalır ve Node.js izlemeyi bırakabilir. v24.0.0'da kararlı hâle geldi.
  • util.transferableAbortSignal(signal): Sinyali structuredClone() ve postMessage() ile aktarılabilir yapar; bir worker'a iptal iletmek için kullanılır. worker_threads yazısına bakın.
  • Dinleyici uyarısı: events.defaultMaxListeners değeri AbortSignal nesnelerini etkilemez; sinyaller varsayılan olarak uyarı vermez, ama isterseniz events.setMaxListeners(n, signal) ile bir uyarı sınırı koyabilirsiniz.
  • DEP0209: v26.8.2'de belge düzeyinde kullanımdan kaldırma (Documentation-only) olarak eklendi. Uzun ömürlü kaynakları (ör. dinleyen bir HTTP sunucusu ya da bir stream) AbortSignal ile kapatmak yerine using / await using ile temizlemeniz önerilir. Belgeye göre AbortSignal işlemleri iptal etmek, iptali dışarıdan iletmek ve zaman aşımı için hâlâ doğru araçtır. Kullanımdan kaldırma türleri için deprecation uyarıları yazısına göz atın.

Sık yapılan hatalar

  • Dinleyiciyi once olmadan eklemek: Uzun yaşayan bir sinyale her çağrıda { once: true } olmadan dinleyici eklemek, belgelerin uyardığı bellek sızıntısıdır. İş bitince dinleyiciyi kaldırın.
  • Sinyalin zaten iptal edilmiş olabileceğini unutmak: 'abort' olayı yalnızca bir kez tetiklenir; iptal edilmiş bir sinyale sonradan eklenen addEventListener dinleyicisi hiç çağrılmaz. Önce signal.aborted kontrolü yapın.
  • Tek bir denetleyiciyi farklı işlerde tekrar kullanmak: Bir kez iptal edilen denetleyici geri alınamaz. Her yeni iş için yeni bir AbortController oluşturun.
  • Hata adını yalnızca AbortError ile kontrol etmek: fetch zaman aşımında TimeoutError gelir; özel sebep verdiyseniz o hatanın adı gelir.
  • İptalin her şeyi geri aldığını sanmak: Belgelere göre readFile iptali işletim sistemi isteklerini durdurmaz; yazma işlemlerinde dosyaya yazılmış kısım olabilir.

Sık Sorulan Sorular

AbortController'ı içe aktarmam gerekir mi?

Hayır. AbortController ve AbortSignal Node.js'te global nesnelerdir; doğrudan new AbortController() yazabilirsiniz.

Bir isteğe zaman aşımı nasıl eklenir?

En kısa yol AbortSignal.timeout(ms)'dir: fetch(url, { signal: AbortSignal.timeout(5000) }). Hem kullanıcı iptali hem zaman aşımı istiyorsanız iki sinyali AbortSignal.any([ac.signal, AbortSignal.timeout(5000)]) ile birleştirin.

abort() çağırdıktan sonra denetleyiciyi sıfırlayabilir miyim?

Hayır. Bir AbortSignal iptal edildikten sonra aborted değeri hep true kalır ve 'abort' olayı bir daha tetiklenmez. Yeni bir iş için yeni bir denetleyici oluşturun.

AbortError ile TimeoutError arasındaki fark nedir?

AbortError genel iptal hatasıdır; TimeoutError, AbortSignal.timeout() ile oluşturulan sinyalin sebebidir. Web API'leri (ör. fetch) bu sebebi doğrudan fırlatabilir, Node.js API'leri ise bir AbortError fırlatıp asıl sebebi cause özelliğine koyabilir.

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: Node.js öğrenme rehberi · Node.js timers ve timers/promises · fetch ile HTTP isteği · EventEmitter kullanımı · Promise.all, race ve any