تصور کنید یک داشبورد مدیریتی را طراحی کردهاید که دادههای یک مدل هوش مصنوعی را بهصورت لحظهای دریافت میکند؛ در این حالت، تنها یک فراخوانی ساده از JSON.parse میتواند کل رابط کاربری شما را به طور ناگهانی متوقف کند. این اتفاق زمانی رخ میدهد که پارسرهای استاندارد مرورگر، هر تکه (chunk) از دادههای ورودی را بهعنوان یک سند کامل در نظر میگیرند و با رسیدن اولین توکنها، خطای نحو (Syntax Error) صادر میکنند.
سهشنبه گذشته، دقیقاً همین سناریو زمانی رخ داد که یک داشبورد نموداری کوچک به سرور رایگان MonkeyCode متصل شد. اولین تکه داده رسید، JSON.parse خطای SyntaxError: Unexpected end of JSON input را صادر کرد و داشبورد کاملاً سفید شد، در حالی که تب شبکه (Network tab) همچنان در حال تحویل بایتهای معتبر بود. در این زنجیره، نه مدل و نه سرور مقصر نبودند؛ بلکه تنها مقصر، پارسر بود.
استریمینگ در واقع قرارداد بنیادین یک API را تغییر میدهد. در حالی که یک درخواست سنتی یک فایل تمامشده را تحویل میدهد، استریم تکههایی را میفرستد که رشتههای متنی معتبری هستند اما اسناد JSON معتبری نیستند. این وضعیت یک «شکاف خاموش» ایجاد میکند؛ جایی که کاربر یا هیچچیز نمیبیند در حالی که سیستم در حال بافر کردن دادههاست، یا در بدترین حالت، با یک صفحه سفید مواجه میشود زیرا پارسر یک استثنا (Exception) پرتاب کرده است.
به گزارش یک راهنمای فنی منتشر شده در dev.to در ۲۲ اوت ۲۰۲۶، متد بومی Response.json() در مرورگرها نیز از همین نقص رنج میبرد. این متد کل بدنه را پیش از رمزگشایی بافر میکند، که عملاً هدف استفاده از استریم برای تعاملات لحظهای با هوش مصنوعی زاینده (Generative AI) — که شبیه به تماشای تایپ شدن زنده یک متن توسط نویسنده است — را از بین میبرد.
مسیر عیبیابی
برای شناسایی ریشه مشکل، توسعهدهندگان میتوانند از یک روال سهمرحلهای برای جداسازی پارسر از شبکه استفاده کنند:
- تأیید سرور: استفاده از دستور
curl -N -X POSTبا فلگ-Nبافرینگ را غیرفعال میکند. این کار تأیید میکند که نقطه انتهایی (Endpoint) واقعاً توکنها را یکییکی میفرستد. برای مثال، پرامپتی که درخواست{"labels":["a","b"],"values":[1,2]}را دارد، باید توکنها را بهصورت افزایشی نشان دهد. - ثبت تکهها (Chunk Logging): ثبت هر تکه از طریق
TextDecoderوresponse.body.getReader()نشان میدهد که دادهها به صورت قطعاتی مثل{"labels":["a"و,"b"],"values":[1,2]}ارسال میشوند. این قطعات تضمینشده است که در یک پارسر استاندارد شکست بخورند. - بررسی قابلیت مدل: تعیین اینکه آیا مدل از «حالت JSON» (JSON mode) بومی پشتیبانی میکند که اشیاء کامل را در هر تکه صادر کند یا خیر. بسیاری از مدلهای رایگان این قابلیت را بهطور پیشفرض ارائه نمیدهند، به این معنی که توسعهدهنده باید منطق رمزگشایی خود را تغییر دهد، بهجای اینکه به رفتار مدل تکیه کند. این چالشها در مدلهای کوچکتر رایجتر است و پیش از این تغییر ساختار داده به مدل تخت به عنوان راهکاری برای رفع خطاهای JSON در مدلهای ۷ میلیارد پارامتری پیشنهاد شده بود.
استراتژی اول: بافر و تلاش مجدد
برای اشیاء JSON تک، سادهترین راه استفاده از یک BufferedJsonParser است. این کلاس تکهها را به یک بافر خصوصی #buffer اضافه کرده و پس از هر ورود جدید، سعی میکند کل رشته را رمزگشایی کند.
از آنجا که JSON.parse خاصیت تکرارپذیری (Idempotent) دارد، تا زمان رسیدن آخرین براکت بسته، صرفاً خطا میدهد. پس از موفقیت، بافر پاک میشود. اما این روش یک هزینه پردازشی دارد؛ تبدیل یک عملیات O(n) به O(n²). این هزینه برای چند هزار توکن ناچیز است، اما برای استریمهایی که به ۱۰۰ هزار توکن میرسند، این هزینه درجه دوم (Quadratic cost) بهشدت قابل توجه میشود.
استراتژی دوم: رمزگشایی خطمحور (JSONL)
اگر توسعهدهنده روی پرامپت کنترل داشته باشد، درخواست فرمت JSON Lines (JSONL) برترین انتخاب معماری است. در این فرمت، هر خط یک شیء JSON مستقل و کامل است.
این ساختار به پارسر اجازه میدهد با استفاده از کاراکتر خط جدید (\n) بافر را تکهتکه کند. خطوط کامل فوراً به رابط کاربری میروند و خطوط ناقص در بافر منتظر میمانند. این الگو رویدادهای پیشرفت طبیعی ایجاد میکند، زیرا هر خط رمزگشاییشده، یک واحد کاری تکمیلشده است که میتواند بهصورت افزایشی اعلام یا رندر شود.
استراتژی سوم: استخراج هدفمند فیلدها
برای نمونهسازی سریع، توسعهدهندگان میتوانند از عبارتهای منظم (Regular Expressions) برای استخراج فیلدهای خاص، مانند یک پیام «وضعیت»، بدون رمزگشایی کل شیء استفاده کنند.
استفاده از الگویی مانند "${fieldName}"\s*:\s*"((?:[^"\\]|\\.)*)" اجازه استخراج یک فیلد رشتهای را میدهد. اگرچه این یک «ترفند» (Hack) است که در مواجهه با آرایهها یا اشیاء تودرتو شکست میخورد، اما بهروزرسانیهای لحظهای برای نمایشگرهای وضعیت ساده مانند "status": "thinking" را بدون انتظار برای تکمیل کل سند ممکن میسازد.
دسترسیپذیری و تأیید نهایی
رمزگشایی تدریجی شکاف بزرگی را در دسترسیپذیری (Accessibility) پر میکند. کاربرانی که از صفحهخوان استفاده میکنند، وقتی رابط کاربری در حین تجمع توکنها ساکت میماند، احساس میکنند سیستم خراب شده است.
با اعلام تغییرات وضعیت — مانند «در حال دریافت دادهها» یا «دادهها تکمیل شد» — از طریق مناطق aria-live="polite"، توسعهدهندگان مدل ذهنی مشابه یک نوار پیشرفت بصری ایجاد میکنند. نکته کلیدی این است که از اعلام هر تکه داده بهصورت جداگانه برای جلوگیری از ایجاد مزاحمت (Spam) برای صفحهخوان خودداری شود؛ در عوض، انتقال از وضعیت «در حال دریافت» به «تکمیل دادهها» یا «خطا در رمزگشایی» اعلام شود.
پیش از انتشار محصول، راهنمای مذکور پیشنهاد میکند یک ماتریس تأیید برای تست حالتهای شکست اجرا شود:
- تکههای کوچک: اطمینان از اینکه
{"a":1}وقتی به صورت{"a", :1}میرسد، فقط پس از تکمیل رمزگشایی شود. - تکههای بزرگ: تأیید اینکه یک شیء کامل در یک تکه، فوراً رمزگشایی شود.
- بخشهای ناقص JSONL: تأیید اینکه خط اول رمزگشایی شده و خط دوم (ناقص) در بافر بماند.
- اشیاء تودرتو: اطمینان از اینکه
{"a":{"b":1}}تنها پس از تکمیل کامل پردازش شود. - نقلقولهای گریزدار (Escaped): تأیید اینکه رشتههایی مثل
"he said \"hi\"باعث خرابی پارسر نشوند. - JSON بدساخت: اطمینان از اینکه خطاهایی مثل
{"a":}بهجای نادیده گرفته شدن، گزارش شوند.
این رویکرد برای کسانی که از محیطهایی مثل سرور رایگان MonkeyCode استفاده میکنند ضروری است که یک محیط بازی برای نمونهسازی فراهم میکند. تا اوت ۲۰۲۶، سهمیه توکن مدل رایگان این سرویس ۱۰ میلیون توکن است؛ عددی که باید بهعنوان یک بودجه برای اندازهگیری و آزمایش دیده شود، نه یک توافقنامه سطح خدمات (SLA) برای محیط تولید، زیرا این سهمیهها میتوانند بدون اطلاع قبلی تغییر کنند.
برای اکثر توسعهدهندگان، تغییر از رمزگشایی استاتیک به تدریجی، مرز بین یک رابط کاربری حرفهای و یک سیستم شکننده است. اگر UI نتواند تا زمان تکمیل شیء چیزی را رندر کند، بافرینگ تنها گزینه است، فارغ از هزینه پردازشی O(n²).
توسعهدهندگان باید اکنون نقاط انتهایی (Endpoints) استریمینگ خود را بررسی کنند تا ببینند آیا امکان تغییر فرمت خروجی به JSONL برای حذف سربار تکراری بافرینگ وجود دارد یا خیر. اگر با سرور رایگان MonkeyCode آزمایش میکنید، به یاد داشته باشید که پارسر خودتان را همراه بیاورید، زیرا JSON.parse شما را نجات نخواهد داد.
گام بعدی شما
- نقاط انتهایی (Endpoints) استریمینگ خود را بررسی کنید تا ببینید آیا امکان تغییر فرمت خروجی به JSONL برای حذف سربار O(n²) وجود دارد یا خیر.
- برای بهبود تجربه کاربری، از
aria-liveبرای اعلام وضعیت دریافت دادهها به کاربران استفاده کنید. - اگر از سرورهای رایگان برای نمونهسازی استفاده میکنید، هرگز به
JSON.parseبومی تکیه نکنید و یک پارسر بافرشده پیادهسازی کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو