مقدمه

در مدل سنتی، برای کار با یک منبع داده (مانند یک فایل بزرگ یا پاسخ یک درخواست شبکه)، ابتدا باید کل منبع را دانلود کرده و در حافظه بارگذاری کنیم و سپس پردازش را شروع کنیم. این روش برای منابع حجیم می‌تواند حافظه زیادی مصرف کرده و باعث تاخیر در شروع پردازش شود. 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 می‌شود.
Copy Icon JAVASCRIPT
async function processStreamedResponse() {
    const response = await fetch('/path/to/large-file.txt');
    
    // Get the reader from the response body stream
    const reader = response.body.getReader();
    
    // To decode the binary chunks back to text
    const decoder = new TextDecoder();
    
    while (true) {
        const { done, value } = await reader.read();
        
        if (done) {
            console.log('Stream finished.');
            break;
        }
        
        // 'value' is a Uint8Array. Decode it to a string.
        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 در جاوااسکریپت آشنا می‌شویم.