مقدمه
در مدل سنتی، برای کار با یک منبع داده (مانند یک فایل بزرگ یا پاسخ یک درخواست شبکه)، ابتدا باید
کل منبع را دانلود کرده و در حافظه بارگذاری کنیم و سپس پردازش را شروع کنیم. این روش برای منابع
حجیم میتواند حافظه زیادی مصرف کرده و باعث تاخیر در شروع پردازش شود. Streams API یک راه
حل مدرن برای این مشکل ارائه میدهد.
یک «جریان» یا Stream، یک دنباله از داده است که در طول زمان در دسترس قرار میگیرد. به جای
بارگذاری کل داده در حافظه، Streams API به ما اجازه میدهد داده را به صورت قطعههای کوچک
(chunks) دریافت کرده و همان موقع پردازش کنیم. این رویکرد دو مزیت بزرگ دارد:
- بهینهسازی حافظه: نیازی به نگهداری کل فایل در حافظه RAM نیست که این امر برای
دستگاههای با منابع محدود حیاتی است.
- افزایش سرعت پاسخدهی: میتوانیم پردازش داده را به محض دریافت اولین قطعه شروع کنیم و
منتظر دانلود کامل منبع نمانیم.
این API شامل سه نوع اصلی جریان است: ReadableStream (برای خواندن داده)،
WritableStream (برای نوشتن داده)، و TransformStream (برای تبدیل داده در حین عبور
از یک جریان). در این درس ما روی رایجترین مورد یعنی خواندن از یک ReadableStream تمرکز
خواهیم کرد.
کار با ReadableStream
بسیاری از APIهای مدرن مرورگر، مانند Fetch API، دادهها را به صورت یک ReadableStream
ارائه میدهند. برای مثال، پراپرتی body در پاسخ یک درخواست fetch، یک جریان قابل خواندن
است.
گرفتن Reader و خواندن Chunks
برای مصرف داده از یک ReadableStream، باید یک «خواننده» (Reader) از آن دریافت کنیم. این
کار با متد getReader() روی جریان انجام میشود. پس از آن، میتوانیم
در یک حلقه، به طور مکرر متد reader.read() را فراخوانی کنیم. این متد
یک Promise برمیگرداند که با یک شیء به فرم { value, done } حل میشود:
- value: قطعهی بعدی داده، که معمولاً یک Uint8Array است.
- done: یک مقدار بولی که اگر جریان به پایان رسیده باشد، true میشود.
JAVASCRIPT
async function processStreamedResponse() {
const response = await fetch('/path/to/large-file.txt');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('Stream finished.');
break;
}
const chunkText = decoder.decode(value, { stream: true });
console.log('Received chunk:', chunkText);
}
}
processStreamedResponse();
در این مثال، پس از ارسال یک درخواست fetch، یک reader از بدنه پاسخ آن میگیریم. سپس در یک
حلقه، منتظر دریافت هر قطعه داده میمانیم. تا زمانی که جریان تمام نشده باشد (done برابر با
false است)، هر قطعه (`value`) را که یک آرایه بایتی است، با استفاده از TextDecoder به
متن تبدیل کرده و پردازش میکنیم. گزینه { stream: true } به دیکودر کمک میکند تا کاراکترهای
چند-بایتی که ممکن است بین دو قطعه تقسیم شده باشند را به درستی مدیریت کند.
اتصال جریانها (Piping)
یکی از قابلیتهای پیشرفته و قدرتمند Streams API، امکان «لولهکشی» یا piping است.
با استفاده از متد pipeTo()، میتوان یک ReadableStream را
مستقیماً به یک WritableStream متصل کرد. این کار به مرورگر اجازه میدهد تا دادهها را به
صورت بهینه و بدون نیاز به دخالت کد جاوااسکریپت ما برای خواندن و نوشتن هر قطعه، منتقل کند.
این الگو برای سناریوهایی مانند دانلود یک فایل و ذخیرهسازی مستقیم آن با File System Access API
یا ارسال دادههای دریافتی به یک TransformStream برای فشردهسازی، بسیار کارآمد است.
در این درس با مفهوم قدرتمند Streams API و نحوه پردازش بهینه دادههای حجیم از طریق خواندن
قطعه به قطعهی یک ReadableStream آشنا شدیم. این API یکی از ابزارهای کلیدی برای ساخت وب
اپلیکیشنهای مدرن و با کارایی بالا، به ویژه در کار با شبکهها و فایلها، محسوب میشود. در درس
بعدی، به سراغ `APIهای URL` خواهیم رفت و با ابزارهای مدرن برای ساخت، تحلیل و دستکاری آدرسهای URL
در جاوااسکریپت آشنا میشویم.