Türkçe

Akış işlemede devrim yaratmak için JavaScript Async Iterator Yardımcılarını keşfedin. map, filter, take, drop ve daha fazlasıyla asenkron veri akışlarını verimli bir şekilde yönetmeyi öğrenin.

JavaScript Async Iterator Yardımcıları: Modern Uygulamalar için Güçlü Akış İşleme

Modern JavaScript geliştirmede, asenkron veri akışlarıyla uğraşmak yaygın bir gerekliliktir. İster bir API'den veri çekiyor, ister büyük dosyaları işliyor, isterse de gerçek zamanlı olayları yönetiyor olun, asenkron verileri verimli bir şekilde yönetmek çok önemlidir. JavaScript'in Async Iterator Yardımcıları, bu akışları işlemek için güçlü ve zarif bir yol sunarak, veri manipülasyonuna işlevsel ve birleştirilebilir bir yaklaşım getirir.

Async Iterator ve Async Iterable Nedir?

Async Iterator Yardımcılarına geçmeden önce, temel kavramları anlayalım: Async Iterator'lar ve Async Iterable'lar.

Bir Async Iterable, değerleri üzerinde asenkron olarak yinelenecek bir yol tanımlayan bir nesnedir. Bunu, bir Async Iterator döndüren @@asyncIterator metodunu uygulayarak yapar.

Bir Async Iterator, bir next() metodu sağlayan bir nesnedir. Bu metod, iki özelliğe sahip bir nesneye çözümlenen (resolve) bir promise döndürür:

İşte basit bir örnek:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Asenkron bir işlemi simüle et
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Çıktı: 1, 2, 3, 4, 5 (her biri arasında 500ms gecikme ile)
  }
})();

Bu örnekte, generateSequence, asenkron olarak bir sayı dizisi üreten bir asenkron generator fonksiyonudur. for await...of döngüsü, asenkron iterable'dan değerleri tüketmek için kullanılır.

Async Iterator Yardımcılarına Giriş

Async Iterator Yardımcıları, Async Iterator'ların işlevselliğini genişleterek, asenkron veri akışlarını dönüştürmek, filtrelemek ve işlemek için bir dizi metod sağlar. Veri işleme boru hatlarını (pipeline) daha kolay oluşturmayı sağlayan işlevsel ve birleştirilebilir bir programlama stili sunarlar.

Temel Async Iterator Yardımcıları şunlardır:

Şimdi her bir yardımcıyı örneklerle inceleyelim.

map()

map() yardımcısı, sağlanan bir fonksiyonu kullanarak asenkron iterable'ın her bir elemanını dönüştürür. Dönüştürülmüş değerlerle yeni bir asenkron iterable döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const doubledIterable = asyncIterable.map(x => x * 2);

(async () => {
  for await (const value of doubledIterable) {
    console.log(value); // Çıktı: 2, 4, 6, 8, 10 (100ms gecikme ile)
  }
})();

Bu örnekte, map(x => x * 2) dizideki her sayıyı ikiyle çarpar.

filter()

filter() yardımcısı, sağlanan bir koşula (predicate fonksiyonu) göre asenkron iterable'dan elemanları seçer. Yalnızca koşulu sağlayan elemanları içeren yeni bir asenkron iterable döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(10);

const evenNumbersIterable = asyncIterable.filter(x => x % 2 === 0);

(async () => {
  for await (const value of evenNumbersIterable) {
    console.log(value); // Çıktı: 2, 4, 6, 8, 10 (100ms gecikme ile)
  }
})();

Bu örnekte, filter(x => x % 2 === 0) diziden yalnızca çift sayıları seçer.

take()

take() yardımcısı, asenkron iterable'dan ilk N elemanı döndürür. Yalnızca belirtilen sayıda elemanı içeren yeni bir asenkron iterable döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const firstThreeIterable = asyncIterable.take(3);

(async () => {
  for await (const value of firstThreeIterable) {
    console.log(value); // Çıktı: 1, 2, 3 (100ms gecikme ile)
  }
})();

Bu örnekte, take(3) diziden ilk üç sayıyı seçer.

drop()

drop() yardımcısı, asenkron iterable'dan ilk N elemanı atlar ve geri kalanını döndürür. Kalan elemanları içeren yeni bir asenkron iterable döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const afterFirstTwoIterable = asyncIterable.drop(2);

(async () => {
  for await (const value of afterFirstTwoIterable) {
    console.log(value); // Çıktı: 3, 4, 5 (100ms gecikme ile)
  }
})();

Bu örnekte, drop(2) diziden ilk iki sayıyı atlar.

toArray()

toArray() yardımcısı, tüm asenkron iterable'ı tüketir ve tüm elemanları bir diziye toplar. Tüm elemanları içeren bir diziye çözümlenen bir promise döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const numbersArray = await asyncIterable.toArray();
  console.log(numbersArray); // Çıktı: [1, 2, 3, 4, 5]
})();

Bu örnekte, toArray() dizideki tüm sayıları bir diziye toplar.

forEach()

forEach() yardımcısı, asenkron iterable'daki her eleman için sağlanan bir fonksiyonu bir kez yürütür. Yeni bir asenkron iterable döndürmez, fonksiyonu yan etki (side-effect) olarak çalıştırır. Bu, loglama veya bir kullanıcı arayüzünü güncelleme gibi işlemler için faydalı olabilir.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(3);

(async () => {
  await asyncIterable.forEach(value => {
    console.log("Değer:", value);
  });
  console.log("forEach tamamlandı");
})();
// Çıktı: Değer: 1, Değer: 2, Değer: 3, forEach tamamlandı

some()

some() yardımcısı, asenkron iterable'daki en az bir elemanın sağlanan fonksiyon tarafından uygulanan testi geçip geçmediğini test eder. Bir boolean değere (en az bir eleman koşulu karşılıyorsa true, aksi takdirde false) çözümlenen bir promise döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const hasEvenNumber = await asyncIterable.some(x => x % 2 === 0);
  console.log("Çift sayı var mı:", hasEvenNumber); // Çıktı: Çift sayı var mı: true
})();

every()

every() yardımcısı, asenkron iterable'daki tüm elemanların sağlanan fonksiyon tarafından uygulanan testi geçip geçmediğini test eder. Bir boolean değere (tüm elemanlar koşulu karşılıyorsa true, aksi takdirde false) çözümlenen bir promise döndürür.


async function* generateSequence(end) {
  for (let i = 2; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(4);

(async () => {
  const areAllEven = await asyncIterable.every(x => x % 2 === 0);
  console.log("Hepsi çift mi:", areAllEven); // Çıktı: Hepsi çift mi: true
})();

find()

find() yardımcısı, asenkron iterable'da sağlanan test fonksiyonunu karşılayan ilk elemanı döndürür. Hiçbir değer test fonksiyonunu karşılamazsa, undefined döndürülür. Bulunan elemana veya undefined'a çözümlenen bir promise döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const firstEven = await asyncIterable.find(x => x % 2 === 0);
  console.log("İlk çift sayı:", firstEven); // Çıktı: İlk çift sayı: 2
})();

reduce()

reduce() yardımcısı, asenkron iterable'ın her elemanı üzerinde kullanıcı tarafından sağlanan bir "reducer" geri arama fonksiyonunu sırayla çalıştırır ve önceki eleman üzerindeki hesaplamadan gelen dönüş değerini aktarır. Reducer'ın tüm elemanlar üzerinde çalıştırılmasının nihai sonucu tek bir değerdir. Son birikmiş değere çözümlenen bir promise döndürür.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const sum = await asyncIterable.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
  console.log("Toplam:", sum); // Çıktı: Toplam: 15
})();

Pratik Örnekler ve Kullanım Alanları

Async Iterator Yardımcıları çeşitli senaryolarda değerlidir. Bazı pratik örneklere göz atalım:

1. Akış API'sinden Gelen Veriyi İşleme

Bir akış API'sinden veri alan gerçek zamanlı bir veri görselleştirme panosu oluşturduğunuzu hayal edin. API sürekli olarak güncellemeler gönderir ve en son bilgileri görüntülemek için bu güncellemeleri işlemeniz gerekir.


async function* fetchDataFromAPI(url) {
  let response = await fetch(url);

  if (!response.body) {
    throw new Error("ReadableStream bu ortamda desteklenmiyor");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      const chunk = decoder.decode(value);
      // API'nin yeni satırlarla ayrılmış JSON nesneleri gönderdiğini varsayalım
      const lines = chunk.split('\n');
      for (const line of lines) {
        if (line.trim() !== '') {
          yield JSON.parse(line);
        }
      }
    }
  } finally {
    reader.releaseLock();
  }
}

const apiURL = 'https://example.com/streaming-api'; // API URL'nizle değiştirin
const dataStream = fetchDataFromAPI(apiURL);

// Veri akışını işle
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('İşlenmiş Veri:', data);
    // Panoyu işlenmiş veriyle güncelle
  }
})();

Bu örnekte, fetchDataFromAPI bir akış API'sinden veri çeker, JSON nesnelerini ayrıştırır ve bunları bir asenkron iterable olarak yield eder. filter yardımcısı yalnızca metrikleri seçer ve map yardımcısı, panoyu güncellemeden önce veriyi istenen formata dönüştürür.

2. Büyük Dosyaları Okuma ve İşleme

Müşteri verileri içeren büyük bir CSV dosyasını işlemeniz gerektiğini varsayalım. Tüm dosyayı belleğe yüklemek yerine, Async Iterator Yardımcılarını kullanarak dosyayı parça parça işleyebilirsiniz.


async function* readLinesFromFile(filePath) {
  const file = await fsPromises.open(filePath, 'r');

  try {
    let buffer = Buffer.alloc(1024);
    let fileOffset = 0;
    let remainder = '';

    while (true) {
      const { bytesRead } = await file.read(buffer, 0, buffer.length, fileOffset);
      if (bytesRead === 0) {
        if (remainder) {
          yield remainder;
        }
        break;
      }

      fileOffset += bytesRead;
      const chunk = buffer.toString('utf8', 0, bytesRead);
      const lines = chunk.split('\n');

      lines[0] = remainder + lines[0];
      remainder = lines.pop() || '';

      for (const line of lines) {
        yield line;
      }
    }
  } finally {
    await file.close();
  }
}

const filePath = './customer_data.csv'; // Dosya yolunuzla değiştirin
const lines = readLinesFromFile(filePath);

// Satırları işle
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('ABD'den Müşteri:', customerData);
    // ABD'den müşteri verilerini işle
  }
})();

Bu örnekte, readLinesFromFile dosyayı satır satır okur ve her satırı bir asenkron iterable olarak yield eder. drop(1) yardımcısı başlık satırını atlar, map yardımcısı satırı sütunlara ayırır ve filter yardımcısı yalnızca ABD'den gelen müşterileri seçer.

3. Gerçek Zamanlı Olayları Yönetme

Async Iterator Yardımcıları, WebSockets gibi kaynaklardan gelen gerçek zamanlı olayları yönetmek için de kullanılabilir. Olaylar geldikçe yayan bir asenkron iterable oluşturabilir ve ardından bu olayları işlemek için yardımcıları kullanabilirsiniz.


async function* createWebSocketStream(url) {
  const ws = new WebSocket(url);

  yield new Promise((resolve, reject) => {
      ws.onopen = () => {
          resolve();
      };
      ws.onerror = (error) => {
          reject(error);
      };
  });

  try {
    while (ws.readyState === WebSocket.OPEN) {
      yield new Promise((resolve, reject) => {
        ws.onmessage = (event) => {
          resolve(JSON.parse(event.data));
        };
        ws.onerror = (error) => {
          reject(error);
        };
        ws.onclose = () => {
           resolve(null); // Bağlantı kapandığında null ile çözümle
        }
      });

    }
  } finally {
    ws.close();
  }
}

const websocketURL = 'wss://example.com/events'; // WebSocket URL'nizle değiştirin
const eventStream = createWebSocketStream(websocketURL);

// Olay akışını işle
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('Kullanıcı Giriş Olayı:', event);
    // Kullanıcı giriş olayını işle
  }
})();

Bu örnekte, createWebSocketStream bir WebSocket'ten alınan olayları yayan bir asenkron iterable oluşturur. filter yardımcısı yalnızca kullanıcı giriş olaylarını seçer ve map yardımcısı veriyi istenen formata dönüştürür.

Async Iterator Yardımcılarını Kullanmanın Faydaları

Tarayıcı ve Çalışma Zamanı Desteği

Async Iterator Yardımcıları JavaScript'te hala nispeten yeni bir özelliktir. 2024'ün sonları itibarıyla, TC39 standardizasyon sürecinin 3. Aşamasındadırlar, bu da yakın gelecekte standartlaştırılma olasılıklarının yüksek olduğu anlamına gelir. Ancak, henüz tüm tarayıcılarda ve Node.js sürümlerinde yerel olarak desteklenmemektedirler.

Tarayıcı Desteği: Chrome, Firefox, Safari ve Edge gibi modern tarayıcılar, Async Iterator Yardımcıları için yavaş yavaş destek eklemektedir. Bu özelliği hangi tarayıcıların desteklediğini görmek için Can I use... gibi web sitelerindeki en son tarayıcı uyumluluk bilgilerini kontrol edebilirsiniz.

Node.js Desteği: Node.js'nin son sürümleri (v18 ve üzeri), Async Iterator Yardımcıları için deneysel destek sağlar. Bunları kullanmak için Node.js'i --experimental-async-iterator bayrağıyla çalıştırmanız gerekebilir.

Polyfill'ler: Async Iterator Yardımcılarını yerel olarak desteklemeyen ortamlarda kullanmanız gerekiyorsa, bir polyfill kullanabilirsiniz. Bir polyfill, eksik işlevselliği sağlayan bir kod parçasıdır. Async Iterator Yardımcıları için birkaç polyfill kütüphanesi mevcuttur; popüler bir seçenek core-js kütüphanesidir.

Özel Async Iterator'ları Uygulama

Async Iterator Yardımcıları, mevcut asenkron iterable'ları işlemek için uygun bir yol sağlarken, bazen kendi özel asenkron iterator'larınızı oluşturmanız gerekebilir. Bu, veritabanları, API'ler veya dosya sistemleri gibi çeşitli kaynaklardan gelen verileri akış halinde işlemenize olanak tanır.

Özel bir asenkron iterator oluşturmak için, bir nesne üzerinde @@asyncIterator metodunu uygulamanız gerekir. Bu metod, bir next() metoduna sahip bir nesne döndürmelidir. next() metodu, value ve done özelliklerine sahip bir nesneye çözümlenen bir promise döndürmelidir.

İşte sayfalandırılmış bir API'den veri çeken özel bir asenkron iterator örneği:


async function* fetchPaginatedData(baseURL) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const url = `${baseURL}?page=${page}`;
    const response = await fetch(url);
    const data = await response.json();

    if (data.results.length === 0) {
      hasMore = false;
      break;
    }

    for (const item of data.results) {
      yield item;
    }

    page++;
  }
}

const apiBaseURL = 'https://api.example.com/data'; // API URL'nizle değiştirin
const paginatedData = fetchPaginatedData(apiBaseURL);

// Sayfalandırılmış veriyi işle
(async () => {
  for await (const item of paginatedData) {
    console.log('Öğe:', item);
    // Öğeyi işle
  }
})();

Bu örnekte, fetchPaginatedData sayfalandırılmış bir API'den veri çeker ve her öğeyi alındıkça yield eder. Asenkron iterator, sayfalama mantığını yöneterek veriyi akış halinde tüketmeyi kolaylaştırır.

Potansiyel Zorluklar ve Dikkat Edilmesi Gerekenler

Async Iterator Yardımcıları çok sayıda fayda sunsa da, bazı potansiyel zorlukların ve dikkat edilmesi gerekenlerin farkında olmak önemlidir:

Async Iterator Yardımcılarını Kullanmak İçin En İyi Uygulamalar

Async Iterator Yardımcılarından en iyi şekilde yararlanmak için aşağıdaki en iyi uygulamaları göz önünde bulundurun:

İleri Düzey Teknikler

Özel Yardımcıları Birleştirme

Mevcut yardımcıları birleştirerek veya sıfırdan yenilerini oluşturarak kendi özel asenkron iterator yardımcılarınızı oluşturabilirsiniz. Bu, işlevselliği özel ihtiyaçlarınıza göre uyarlamanıza ve yeniden kullanılabilir bileşenler oluşturmanıza olanak tanır.


async function* takeWhile(asyncIterable, predicate) {
  for await (const value of asyncIterable) {
    if (!predicate(value)) {
      break;
    }
    yield value;
  }
}

// Örnek Kullanım:
async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(10);
const firstFive = takeWhile(asyncIterable, x => x <= 5);

(async () => {
  for await (const value of firstFive) {
    console.log(value);
  }
})();

Birden Fazla Asenkron Iterable'ı Birleştirme

zip veya merge gibi teknikler kullanarak birden fazla asenkron iterable'ı tek bir asenkron iterable'da birleştirebilirsiniz. Bu, birden fazla kaynaktan gelen veriyi aynı anda işlemenize olanak tanır.


async function* zip(asyncIterable1, asyncIterable2) {
    const iterator1 = asyncIterable1[Symbol.asyncIterator]();
    const iterator2 = asyncIterable2[Symbol.asyncIterator]();

    while (true) {
        const result1 = await iterator1.next();
        const result2 = await iterator2.next();

        if (result1.done || result2.done) {
            break;
        }

        yield [result1.value, result2.value];
    }
}

// Örnek Kullanım:
async function* generateSequence1(end) {
    for (let i = 1; i <= end; i++) {
        yield i;
    }
}

async function* generateSequence2(end) {
    for (let i = 10; i <= end + 9; i++) {
        yield i;
    }
}

const iterable1 = generateSequence1(5);
const iterable2 = generateSequence2(5);

(async () => {
    for await (const [value1, value2] of zip(iterable1, iterable2)) {
        console.log(value1, value2);
    }
})();

Sonuç

JavaScript Async Iterator Yardımcıları, asenkron veri akışlarını işlemek için güçlü ve zarif bir yol sunar. Veri manipülasyonuna işlevsel ve birleştirilebilir bir yaklaşım getirerek, karmaşık veri işleme boru hatları oluşturmayı kolaylaştırırlar. Async Iterator'lar ve Async Iterable'ların temel kavramlarını anlayarak ve çeşitli yardımcı metodlarda ustalaşarak, asenkron JavaScript kodunuzun verimliliğini ve sürdürülebilirliğini önemli ölçüde artırabilirsiniz. Tarayıcı ve çalışma zamanı desteği artmaya devam ettikçe, Async Iterator Yardımcıları modern JavaScript geliştiricileri için vazgeçilmez bir araç olmaya adaydır.