JavaScript 'scan' Asenkron Yineleyici Yardımcısının işlevselliğini, kullanım alanlarını ve asenkron kümülatif işleme faydalarını derinlemesine inceliyoruz.
JavaScript Asenkron Yineleyici Yardımcısı: Scan - Asenkron Kümülatif İşleme
Asenkron programlama, özellikle ağ istekleri veya dosya sistemi etkileşimleri gibi G/Ç-bağımlı işlemlerle uğraşırken modern JavaScript geliştirmenin temel taşlarından biridir. ES2018'de tanıtılan asenkron yineleyiciler, asenkron veri akışlarını işlemek için güçlü bir mekanizma sağlar. Genellikle RxJS gibi kütüphanelerde bulunan ve giderek tek başına bir yardımcı program olarak sunulan `scan` yardımcısı, bu asenkron veri akışlarını işlemek için daha da fazla potansiyel ortaya çıkarır.
Asenkron Yineleyicileri Anlamak
`scan`'e dalmadan önce, asenkron yineleyicilerin ne olduğunu özetleyelim. Bir asenkron yineleyici, asenkron yineleyici protokolüne uyan bir nesnedir. Bu protokol, iki özelliğe sahip bir nesneye çözümlenen bir promise döndüren bir `next()` metodu tanımlar: `value` (dizideki bir sonraki değer) ve `done` (yineleyicinin bitip bitmediğini gösteren bir boolean). Asenkron yineleyiciler, özellikle zamanla gelen verilerle veya alınması için asenkron işlemler gerektiren verilerle çalışırken kullanışlıdır.
İşte temel bir asenkron yineleyici örneği:
async function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
async function main() {
const iterator = generateNumbers();
let result = await iterator.next();
console.log(result); // { value: 1, done: false }
result = await iterator.next();
console.log(result); // { value: 2, done: false }
result = await iterator.next();
console.log(result); // { value: 3, done: false }
result = await iterator.next();
console.log(result); // { value: undefined, done: true }
}
main();
`scan` Yardımcısıyla Tanışın
`scan` yardımcısı (aynı zamanda `accumulate` veya `reduce` olarak da bilinir), bir akümülatör fonksiyonunu her bir değere uygulayarak ve birikmiş sonucu yayınlayarak bir asenkron yineleyiciyi dönüştürür. Bu, dizilerdeki `reduce` metoduna benzer, ancak asenkron olarak ve yineleyiciler üzerinde çalışır.
Özünde, `scan` bir asenkron yineleyici, bir akümülatör fonksiyonu ve isteğe bağlı bir başlangıç değeri alır. Kaynak yineleyici tarafından yayınlanan her değer için, akümülatör fonksiyonu önceki birikmiş değerle (veya ilk yineleme ise başlangıç değeriyle) ve yineleyiciden gelen mevcut değerle çağrılır. Akümülatör fonksiyonunun sonucu, bir sonraki birikmiş değer olur ve bu değer daha sonra sonuçtaki asenkron yineleyici tarafından yayınlanır.
Sözdizimi ve Parametreler
`scan` kullanımı için genel sözdizimi aşağıdaki gibidir:
async function* scan(sourceIterator, accumulator, initialValue) {
let accumulatedValue = initialValue;
for await (const value of sourceIterator) {
accumulatedValue = accumulator(accumulatedValue, value);
yield accumulatedValue;
}
}
- `sourceIterator`: Dönüştürülecek asenkron yineleyici.
- `accumulator`: İki argüman alan bir fonksiyon: önceki birikmiş değer ve yineleyiciden gelen mevcut değer. Yeni birikmiş değeri döndürmelidir.
- `initialValue` (isteğe bağlı): Akümülatör için başlangıç değeri. Sağlanmazsa, kaynak yineleyiciden gelen ilk değer başlangıç değeri olarak kullanılır ve akümülatör fonksiyonu ikinci değerle başlayarak çağrılır.
Kullanım Alanları ve Örnekler
`scan` yardımcısı inanılmaz derecede çok yönlüdür ve asenkron veri akışlarını içeren çok çeşitli senaryolarda kullanılabilir. İşte birkaç örnek:
1. Anlık Toplam Hesaplama
İşlem tutarlarını yayınlayan bir asenkron yineleyiciniz olduğunu hayal edin. Bu işlemlerin anlık toplamını hesaplamak için `scan` kullanabilirsiniz.
async function* generateTransactions() {
yield 10;
yield 20;
yield 30;
}
async function main() {
const transactions = generateTransactions();
const runningTotals = scan(transactions, (acc, value) => acc + value, 0);
for await (const total of runningTotals) {
console.log(total); // Output: 10, 30, 60
}
}
main();
Bu örnekte, `accumulator` fonksiyonu sadece mevcut işlem tutarını önceki toplama ekler. 0 olan `initialValue`, anlık toplamın sıfırdan başlamasını sağlar.
2. Verileri Bir Dizide Biriktirme
Bir asenkron yineleyiciden gelen verileri bir dizide biriktirmek için `scan` kullanabilirsiniz. Bu, zaman içinde veri toplamak ve bunları toplu olarak işlemek için kullanışlı olabilir.
async function* fetchData() {
yield { id: 1, name: 'Alice' };
yield { id: 2, name: 'Bob' };
yield { id: 3, name: 'Charlie' };
}
async function main() {
const dataStream = fetchData();
const accumulatedData = scan(dataStream, (acc, value) => [...acc, value], []);
for await (const data of accumulatedData) {
console.log(data); // Output: [{id: 1, name: 'Alice'}], [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}], [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}, {id: 3, name: 'Charlie'}]
}
}
main();
Burada, `accumulator` fonksiyonu, önceki tüm elemanları ve mevcut değeri içeren yeni bir dizi oluşturmak için spread operatörünü (`...`) kullanır. `initialValue` boş bir dizidir.
3. Bir Hız Sınırlayıcı Uygulama
Daha karmaşık bir kullanım durumu, bir hız sınırlayıcı uygulamaktır. Belirli bir zaman aralığında yapılan istek sayısını izlemek ve hız sınırı aşılırsa sonraki istekleri geciktirmek için `scan` kullanabilirsiniz.
async function* generateRequests() {
// Simulate incoming requests
yield Date.now();
await new Promise(resolve => setTimeout(resolve, 200));
yield Date.now();
await new Promise(resolve => setTimeout(resolve, 100));
yield Date.now();
}
async function main() {
const requests = generateRequests();
const rateLimitWindow = 1000; // 1 second
const maxRequestsPerWindow = 2;
async function* rateLimitedRequests(source, window, maxRequests) {
let queue = [];
for await (const requestTime of source) {
queue.push(requestTime);
queue = queue.filter(t => requestTime - t < window);
if (queue.length > maxRequests) {
const earliestRequest = queue[0];
const delay = window - (requestTime - earliestRequest);
console.log(`Rate limit exceeded. Delaying for ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
yield requestTime;
}
}
const limited = rateLimitedRequests(requests, rateLimitWindow, maxRequestsPerWindow);
for await (const requestTime of limited) {
console.log(`Request processed at ${requestTime}`);
}
}
main();
Bu örnek, istek zaman damgalarından oluşan bir kuyruğu korumak için dahili olarak (`rateLimitedRequests` fonksiyonunda) `scan` kullanır. Hız sınırı aralığındaki istek sayısının izin verilen maksimumu aşıp aşmadığını kontrol eder. Eğer aşarsa, gerekli gecikmeyi hesaplar ve isteği yayınlamadan önce duraklar.
4. Gerçek Zamanlı Bir Veri Toplayıcı Oluşturma (Küresel Örnek)
Çeşitli borsalardan gerçek zamanlı hisse senedi fiyatlarını toplaması gereken küresel bir finansal uygulama düşünün. Bir asenkron yineleyici, New York Borsası (NYSE), Londra Borsası (LSE) ve Tokyo Borsası (TSE) gibi borsalardan fiyat güncellemelerini aktarabilir. `scan`, belirli bir hisse senedi için tüm borsalardaki anlık ortalama veya en yüksek/düşük fiyatı korumak için kullanılabilir.
// Simulate streaming stock prices from different exchanges
async function* generateStockPrices() {
yield { exchange: 'NYSE', symbol: 'AAPL', price: 170.50 };
yield { exchange: 'LSE', symbol: 'AAPL', price: 170.75 };
await new Promise(resolve => setTimeout(resolve, 50));
yield { exchange: 'TSE', symbol: 'AAPL', price: 170.60 };
}
async function main() {
const stockPrices = generateStockPrices();
// Use scan to calculate a running average price
const runningAverages = scan(
stockPrices,
(acc, priceUpdate) => {
const { total, count } = acc;
return { total: total + priceUpdate.price, count: count + 1 };
},
{ total: 0, count: 0 }
);
for await (const averageData of runningAverages) {
const averagePrice = averageData.total / averageData.count;
console.log(`Running average price: ${averagePrice.toFixed(2)}`);
}
}
main();
Bu örnekte, `accumulator` fonksiyonu, fiyatların anlık toplamını ve alınan güncellemelerin sayısını hesaplar. Nihai ortalama fiyat daha sonra bu birikmiş değerlerden hesaplanır. Bu, farklı küresel piyasalardaki hisse senedi fiyatının gerçek zamanlı bir görünümünü sağlar.
5. Web Sitesi Trafiğini Küresel Olarak Analiz Etme
Dünyanın dört bir yanındaki sunuculardan web sitesi ziyaret verisi akışları alan küresel bir web analitik platformu hayal edin. Her veri noktası, web sitesini ziyaret eden bir kullanıcıyı temsil eder. `scan` kullanarak, ülkelere göre sayfa görüntüleme eğilimini gerçek zamanlı olarak analiz edebiliriz. Verilerin şöyle göründüğünü varsayalım: `{ country: "US", page: "homepage", timestamp: 1678886400 }`.
async function* generateWebsiteVisits() {
yield { country: 'US', page: 'homepage', timestamp: Date.now() };
yield { country: 'CA', page: 'product', timestamp: Date.now() };
yield { country: 'UK', page: 'blog', timestamp: Date.now() };
yield { country: 'US', page: 'product', timestamp: Date.now() };
}
async function main() {
const visitStream = generateWebsiteVisits();
const pageViewCounts = scan(
visitStream,
(acc, visit) => {
const { country } = visit;
const newAcc = { ...acc };
newAcc[country] = (newAcc[country] || 0) + 1;
return newAcc;
},
{}
);
for await (const counts of pageViewCounts) {
console.log('Page view counts by country:', counts);
}
}
main();
Burada, `accumulator` fonksiyonu her ülke için bir sayacı günceller. Çıktı, yeni ziyaret verileri geldikçe her ülke için biriken sayfa görüntüleme sayılarını gösterir.
`scan` Kullanmanın Faydaları
`scan` yardımcısı, asenkron veri akışlarıyla çalışırken çeşitli avantajlar sunar:
- Bildirimsel Stil: `scan`, kümülatif işleme mantığını bildirimsel ve öz bir şekilde ifade etmenize olanak tanıyarak kod okunabilirliğini ve sürdürülebilirliğini artırır.
- Asenkron Yönetim: Akümülatör fonksiyonu içindeki asenkron işlemleri sorunsuz bir şekilde yönetir, bu da onu G/Ç-bağımlı görevler içeren karmaşık senaryolar için uygun hale getirir.
- Gerçek Zamanlı İşleme: `scan`, veri akışlarının gerçek zamanlı işlenmesini sağlar, bu da değişikliklere meydana geldikçe tepki vermenize olanak tanır.
- Birleştirilebilirlik: Karmaşık veri işleme boru hatları oluşturmak için diğer asenkron yineleyici yardımcılarıyla kolayca birleştirilebilir.
`scan` Uygulaması (Mevcut Değilse)
Bazı kütüphaneler yerleşik bir `scan` yardımcısı sağlarken, gerekirse kendi uygulamanızı kolayca yapabilirsiniz. İşte basit bir uygulama:
async function* scan(sourceIterator, accumulator, initialValue) {
let accumulatedValue = initialValue;
let first = true;
for await (const value of sourceIterator) {
if (first && initialValue === undefined) {
accumulatedValue = value;
first = false;
} else {
accumulatedValue = accumulator(accumulatedValue, value);
}
yield accumulatedValue;
}
}
Bu uygulama, kaynak yineleyici üzerinde yinelenir ve her değere akümülatör fonksiyonunu uygulayarak birikmiş sonucu yayınlar. `initialValue` sağlanmadığı durumu, kaynak yineleyiciden gelen ilk değeri başlangıç değeri olarak kullanarak yönetir.
`reduce` ile Karşılaştırma
`scan`'i `reduce`'dan ayırmak önemlidir. Her ikisi de yineleyiciler üzerinde çalışır ve bir akümülatör fonksiyonu kullanır, ancak davranışları ve çıktıları bakımından farklılık gösterirler.
- `scan`, her yineleme için birikmiş değeri yayınlayarak birikimin anlık bir geçmişini sağlar.
- `reduce`, yineleyicideki tüm elemanları işledikten sonra yalnızca nihai birikmiş değeri yayınlar.
Bu nedenle, `scan` birikimin ara durumlarını izlemeniz gereken senaryolar için uygunken, `reduce` yalnızca nihai sonuca ihtiyacınız olduğunda uygundur.
Hata Yönetimi
Asenkron yineleyiciler ve `scan` ile çalışırken hataları zarif bir şekilde yönetmek çok önemlidir. Hatalar, yineleme sürecinde veya akümülatör fonksiyonu içinde meydana gelebilir. Bu hataları yakalamak ve yönetmek için `try...catch` bloklarını kullanabilirsiniz.
async function* generatePotentiallyFailingData() {
yield 1;
yield 2;
throw new Error('Something went wrong!');
yield 3;
}
async function main() {
const dataStream = generatePotentiallyFailingData();
try {
const accumulatedData = scan(dataStream, (acc, value) => acc + value, 0);
for await (const data of accumulatedData) {
console.log(data);
}
} catch (error) {
console.error('An error occurred:', error);
}
}
main();
Bu örnekte, `try...catch` bloğu `generatePotentiallyFailingData` yineleyicisi tarafından fırlatılan hatayı yakalar. Ardından hatayı, günlüğe kaydetmek veya işlemi yeniden denemek gibi uygun şekilde yönetebilirsiniz.
Sonuç
`scan` yardımcısı, JavaScript asenkron yineleyicilerinde asenkron kümülatif işleme yapmak için güçlü bir araçtır. Karmaşık veri dönüşümlerini bildirimsel ve öz bir şekilde ifade etmenizi, asenkron işlemleri zarif bir şekilde yönetmenizi ve veri akışlarını gerçek zamanlı olarak işlemenizi sağlar. İşlevselliğini ve kullanım alanlarını anlayarak, daha sağlam ve verimli asenkron uygulamalar oluşturmak için `scan`'den yararlanabilirsiniz. Anlık toplamları hesaplıyor, verileri dizilerde biriktiriyor, hız sınırlayıcılar uyguluyor veya gerçek zamanlı veri toplayıcıları oluşturuyor olun, `scan` kodunuzu basitleştirebilir ve genel performansını artırabilir. Hata yönetimini göz önünde bulundurmayı ve asenkron veri akışlarınızı işlerken ara birikmiş değerlere erişmeniz gerektiğinde `reduce` yerine `scan`'i seçmeyi unutmayın. Exploring libraries like RxJS can further enhance your understanding and practical application of `scan` within reactive programming paradigms.