JavaScript Async Iterator हेल्पर 'स्कॅन' चा सखोल अभ्यास, त्याची कार्यक्षमता, उपयोग आणि असंक्रोनस संचय प्रक्रियेसाठी त्याचे फायदे.
JavaScript Async Iterator हेल्पर: स्कॅन - असिंक्रोनस संचय प्रक्रिया
आधुनिक JavaScript डेव्हलपमेंटमध्ये, विशेषत: I/O-संबंधित ऑपरेशन्स जसे की नेटवर्क विनंत्या किंवा फाइल सिस्टम इंटरॅक्शन (file system interactions) हाताळताना, असंक्रोनस प्रोग्रामिंग हा एक महत्त्वाचा घटक आहे. ES2018 मध्ये सादर केलेले Async iterators, असंक्रोनस डेटाचे प्रवाह हाताळण्यासाठी एक शक्तिशाली यंत्रणा प्रदान करतात. `scan` हेल्पर, जे RxJS सारख्या लायब्ररींमध्ये आढळतात आणि स्टँडअलोन युटिलिटी म्हणून अधिकाधिक उपलब्ध आहेत, हे असंक्रोनस डेटा प्रवाहांवर प्रक्रिया करण्यासाठी आणखी क्षमता अनलॉक करते.
Async Iterators समजून घेणे
`scan` मध्ये जाण्यापूर्वी, async iterators काय आहेत, हे आठवूया. Async iterator हे एक ऑब्जेक्ट आहे जे async iterator प्रोटोकॉलचे पालन करते. हे प्रोटोकॉल `next()` पद्धत (method) परिभाषित करते जी दोन गुणधर्मांसह ऑब्जेक्टवर निराकरण (resolve) होणारे प्रॉमिसे (promise) परत करते: `value` (क्रमाने पुढील मूल्य) आणि `done` (बूलियन जे दर्शविते की पुनरावृत्ती पूर्ण झाली आहे की नाही). Async iterators विशेषतः अशा डेटावर कार्य करताना उपयुक्त आहेत जे वेळेनुसार येतात किंवा डेटा ज्यास आणण्यासाठी असंक्रोनस ऑपरेशन्सची आवश्यकता असते.
येथे async iterator चे एक मूलभूत उदाहरण आहे:
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` हेल्परची ओळख
`scan` हेल्पर (ज्याला `accumulate` किंवा `reduce` म्हणून देखील ओळखले जाते) प्रत्येक मूल्यावर एक्युमुलेटर (accumulator) फंक्शन (function) लागू करून आणि संचित (accumulated) परिणाम उत्सर्जित करून एक async iterator रूपांतरित (transforms) करते. हे ॲरेवरील `reduce` पद्धतीसारखेच आहे, परंतु असंक्रोनसपणे आणि पुनरावृत्तीवर (iterators) कार्य करते.
एका दृष्टीने, `scan` एक async iterator, एक एक्युमुलेटर फंक्शन आणि एक पर्यायी प्रारंभिक मूल्य (initial value) घेते. स्त्रोत पुनरावृत्तीद्वारे (source iterator) उत्सर्जित (emitted) प्रत्येक मूल्यासाठी, एक्युमुलेटर फंक्शन मागील संचित मूल्य (accumulated value) (किंवा प्रारंभिक मूल्य, जर ती पहिली पुनरावृत्ती असेल तर) आणि पुनरावृत्तीतील (iterator) वर्तमान मूल्यासह कॉल केले जाते. एक्युमुलेटर फंक्शनचा परिणाम पुढील संचित मूल्य बनतो, जे नंतर परिणामी async iterator द्वारे उत्सर्जित होते.
सिंटॅक्स आणि पॅरामीटर्स
`scan` वापरण्याचा सामान्य सिंटॅक्स खालीलप्रमाणे आहे:
async function* scan(sourceIterator, accumulator, initialValue) {
let accumulatedValue = initialValue;
for await (const value of sourceIterator) {
accumulatedValue = accumulator(accumulatedValue, value);
yield accumulatedValue;
}
}
- `sourceIterator`: रूपांतरित करण्यासाठी async iterator.
- `accumulator`: एक फंक्शन जे दोन युक्तिवाद (arguments) घेते: मागील संचित मूल्य आणि पुनरावृत्तीतील (iterator) वर्तमान मूल्य. याने नवीन संचित मूल्य परत (return) केले पाहिजे.
- `initialValue` (पर्यायी): एक्युमुलेटरसाठी प्रारंभिक मूल्य. प्रदान (provided) केले नसल्यास, स्त्रोत पुनरावृत्तीतील (source iterator) पहिले मूल्य प्रारंभिक मूल्य म्हणून वापरले जाईल आणि एक्युमुलेटर फंक्शन दुसऱ्या मूल्यापासून सुरू होणारे कॉल केले जाईल.
उपयोग आणि उदाहरणे
`scan` हेल्पर अत्यंत बहुमुखी (versatile) आहे आणि असंक्रोनस डेटा प्रवाहांचा समावेश असलेल्या अनेक परिस्थितींमध्ये वापरला जाऊ शकतो. येथे काही उदाहरणे दिली आहेत:
1. चालु एकूणची गणना करणे
कल्पना करा की तुमच्याकडे एक async iterator आहे जे व्यवहार (transaction) रक्कम उत्सर्जित करते. तुम्ही या व्यवहारांची चालु एकूण (running total) गणना करण्यासाठी `scan` वापरू शकता.
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();
या उदाहरणामध्ये, `accumulator` फंक्शन फक्त वर्तमान व्यवहार रक्कम मागील एकूणमध्ये (total) যোগ करते. 0 चे `initialValue` हे सुनिश्चित करते की चालु एकूण शून्यापासून सुरू होते.
2. डेटा ॲरेमध्ये जमा करणे
तुम्ही async iterator मधील डेटा ॲरेमध्ये जमा करण्यासाठी `scan` वापरू शकता. कालांतराने डेटा संकलित (collect) करणे आणि तो बॅचमध्ये (batches) प्रक्रिया करणे उपयुक्त ठरू शकते.
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();
येथे, `accumulator` फंक्शन मागील सर्व घटक आणि वर्तमान मूल्य असलेले नवीन ॲरे तयार करण्यासाठी स्प्रेड ऑपरेटर (`...`) वापरते. `initialValue` एक रिक्त ॲरे आहे.
3. दर मर्यादा (Rate Limiter) लागू करणे
अधिक जटिल उपयोग म्हणजे दर मर्यादा (rate limiter) लागू करणे. तुम्ही एका विशिष्ट वेळेच्या विंडोमध्ये (time window) केलेल्या विनंत्यांची संख्या ट्रॅक (track) करण्यासाठी `scan` वापरू शकता आणि दर मर्यादा ओलांडल्यास (exceeded) पुढील विनंत्या विलंबित (delay) करू शकता.
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();
हे उदाहरण `scan` चा अंतर्गत (internal) वापर करते (rateLimitedRequests फंक्शनमध्ये) विनंती टाइमस्टॅम्पची (timestamp) एक रांग (queue) राखण्यासाठी. हे तपासते की दर मर्यादा विंडोमधील विनंत्यांची संख्या जास्तीत जास्त (maximum) अनुमत (allowed) पेक्षा जास्त आहे का. तसे असल्यास, आवश्यक विलंब (delay) मोजले जाते आणि विनंती देण्यापूर्वी (yielding) थांबवले जाते.
4. रिअल-टाइम डेटा एकत्रित (Aggregator) तयार करणे (ग्लोबल उदाहरण)
एका जागतिक (global) वित्तीय (financial) ॲप्लिकेशनचा विचार करा ज्याला विविध (various) एक्सचेंजमधून (exchanges) रिअल-टाइम स्टॉकच्या किमती एकत्रित करण्याची आवश्यकता आहे. एक async iterator न्यूयॉर्क स्टॉक एक्सचेंज (NYSE), लंडन स्टॉक एक्सचेंज (LSE), आणि टोकियो स्टॉक एक्सचेंज (TSE) सारख्या एक्सचेंजमधून किंमत अद्यतने (updates) प्रवाहित (stream) करू शकतो. `scan` चा उपयोग विशिष्ट स्टॉकसाठी सरासरी किंवा उच्च/कमी किंमत राखण्यासाठी केला जाऊ शकतो.
// 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();
या उदाहरणामध्ये, `accumulator` फंक्शन किंमतीची चालु बेरीज (running total) आणि प्राप्त झालेल्या अद्यतनांची संख्या मोजते. त्यानंतर या संचित मूल्यांमधून अंतिम सरासरी किंमत मोजली जाते. हे विविध जागतिक बाजारात स्टॉकच्या किमतीचे रिअल-टाइम दृश्य (real-time view) प्रदान करते.
5. वेबसाइट रहदारीचे जागतिक विश्लेषण
एका जागतिक वेब विश्लेषण प्लॅटफॉर्मची कल्पना करा जे जगभरातील सर्व्हरवरून वेबसाइट भेटींचा डेटा (visit data) प्राप्त करते. प्रत्येक डेटा पॉइंट (data point) वेबसाइटला भेट देणाऱ्या वापरकर्त्याचे प्रतिनिधित्व करतो. `scan` वापरून, आम्ही प्रति देश (country) पृष्ठ दृश्यांचा (page views) ट्रेंड (trend) वास्तविक वेळेत (real time) विश्लेषण करू शकतो. समजा डेटा असा दिसतो: `{ 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();
येथे, `accumulator` फंक्शन प्रत्येक देशासाठी एक काउंटर (counter) अपडेट करते. आउटपुटमध्ये नवीन व्हिजिट डेटा (visit data) आल्यावर प्रत्येक देशासाठी जमा होणारे पृष्ठ दृश्य मोजले जातील.
`scan` वापरण्याचे फायदे
`scan` हेल्पर असंक्रोनस डेटा प्रवाहावर कार्य करताना अनेक फायदे (advantages) देते:
- घोषणात्मक शैली: `scan` तुम्हाला संचय प्रक्रिया तर्कशास्त्र (accumulative processing logic) घोषणात्मक (declarative) आणि संक्षिप्त (concise) पद्धतीने व्यक्त (express) करण्यास अनुमती देते, ज्यामुळे कोडची सुलभता आणि देखरेखक्षमता सुधारते.
- असिंक्रोनस हाताळणी: हे एक्युमुलेटर फंक्शनमध्ये (accumulator function) असंक्रोनस ऑपरेशन्स (asynchronous operations) अखंडपणे (seamlessly) हाताळते, ज्यामुळे ते I/O-संबंधित कार्यांचा (I/O-bound tasks) समावेश असलेल्या जटिल परिस्थितीसाठी योग्य बनते.
- रिअल-टाइम प्रक्रिया: `scan` तुम्हाला डेटा प्रवाहांची रिअल-टाइम प्रक्रिया सक्षम करते, ज्यामुळे तुम्हाला बदलानुसार (changes) प्रतिक्रिया देता येते.
- कॉम्पोजेबिलिटी: जटिल डेटा प्रोसेसिंग पाइपलाइन (data processing pipelines) तयार करण्यासाठी ते इतर async iterator हेल्पर्ससोबत सहजपणे तयार केले जाऊ शकते.
`scan` ची अंमलबजावणी (Implementation) (जर ते उपलब्ध नसेल तर)
काही लायब्ररी एक अंगभूत (built-in) `scan` हेल्पर प्रदान करतात, परंतु आवश्यक असल्यास तुम्ही स्वतःचे सहज अंमलबजावणी करू शकता. येथे एक सोपे अंमलबजावणी आहे:
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;
}
}
ही अंमलबजावणी स्त्रोत पुनरावृत्तीवर (source iterator) पुनरावृत्ती करते आणि प्रत्येक मूल्यावर एक्युमुलेटर फंक्शन लागू करते, संचित परिणाम (accumulated result) देते. हे `initialValue` प्रदान न केल्यास, स्त्रोत पुनरावृत्तीतील (source iterator) पहिले मूल्य प्रारंभिक मूल्य म्हणून वापरले जाते.
`reduce` शी तुलना
`scan` आणि `reduce` वेगळे करणे महत्त्वाचे आहे. जरी दोन्ही पुनरावृत्तीवर (iterators) कार्य करतात आणि एक एक्युमुलेटर फंक्शन वापरतात, तरी ते त्यांच्या वर्तनात (behavior) आणि आउटपुटमध्ये (output) भिन्न (differ) आहेत.
- `scan` प्रत्येक पुनरावृत्तीसाठी संचित मूल्य उत्सर्जित करते, संचयनाचा (accumulation) चालु इतिहास (running history) प्रदान करते.
- `reduce` पुनरावृत्तीतील (iterator) सर्व घटक प्रक्रिया (process) केल्यानंतर फक्त अंतिम संचित मूल्य उत्सर्जित करते.
म्हणूनच, `scan` अशा परिस्थितीसाठी योग्य आहे जेथे तुम्हाला संचयनाच्या (accumulation) मध्यवर्ती (intermediate) स्थितीचा मागोवा (track) घेण्याची आवश्यकता आहे, तर `reduce` योग्य आहे जेव्हा तुम्हाला फक्त अंतिम निकालाची आवश्यकता असते.
त्रुटी हाताळणी (Error Handling)
असिंक्रोनस पुनरावृत्ती आणि `scan` सह कार्य करताना, त्रुटी चांगल्या प्रकारे हाताळणे (handle gracefully) आवश्यक आहे. पुनरावृत्ती प्रक्रियेदरम्यान (iteration process) किंवा एक्युमुलेटर फंक्शनमध्ये त्रुटी येऊ शकतात. तुम्ही या त्रुटी पकडण्यासाठी (catch) आणि हाताळण्यासाठी `try...catch` ब्लॉक्स (blocks) वापरू शकता.
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();
या उदाहरणामध्ये, `try...catch` ब्लॉक `generatePotentiallyFailingData` पुनरावृत्तीद्वारे (iterator) फेकलेली त्रुटी पकडतो. त्यानंतर तुम्ही त्रुटी योग्यरित्या हाताळू शकता, जसे की लॉग करणे (logging) किंवा ऑपरेशन पुन्हा करणे.
निष्कर्ष
`scan` हेल्पर JavaScript async iterators वर असंक्रोनस संचय प्रक्रिया (asynchronous accumulative processing) करण्यासाठी एक शक्तिशाली साधन आहे. हे तुम्हाला जटिल डेटा रूपांतरणे (data transformations) घोषणात्मक (declarative) आणि संक्षिप्त (concise) पद्धतीने व्यक्त करण्यास, असंक्रोनस ऑपरेशन्स चांगल्या प्रकारे हाताळण्यास आणि रिअल-टाइममध्ये डेटा प्रवाहांवर प्रक्रिया करण्यास सक्षम करते. त्याची कार्यक्षमता (functionality) आणि उपयोग (use cases) समजून घेऊन, तुम्ही अधिक मजबूत आणि कार्यक्षम (efficient) असंक्रोनस ॲप्लिकेशन्स (asynchronous applications) तयार करण्यासाठी `scan` चा उपयोग करू शकता. मग ते चालु बेरीजची गणना (running totals) करणे, ॲरेमध्ये डेटा जमा करणे, दर मर्यादा लागू करणे किंवा रिअल-टाइम डेटा एकत्रित करणे (real-time data aggregators) असो, `scan` तुमचा कोड (code) सुलभ करू शकते आणि त्याची एकूण कार्यक्षमता सुधारू शकते. त्रुटी हाताळणीचा विचार (error handling) करा आणि जेव्हा तुम्हाला तुमच्या असंक्रोनस डेटा प्रवाहांच्या प्रक्रियेदरम्यान मध्यवर्ती संचित मूल्यांमध्ये (intermediate accumulated values) प्रवेश (access) आवश्यक असेल तेव्हा `reduce` पेक्षा `scan` निवडा. RxJS सारख्या लायब्ररींचे (libraries) अन्वेषण (exploring) प्रतिक्रियात्मक प्रोग्रामिंग प्रतिमानांमध्ये (reactive programming paradigms) `scan` च्या तुमच्या समजुतीस (understanding) आणि व्यावहारिक अनुप्रयोगास (practical application) आणखी वाढवू शकते.