۱۲. ذخیره و بازیابی؛ نسخهٔ داده، پشتیبان و شکست قابل مشاهده
در برنامهٔ واقعی، موفقیت امروز کافی نیست؛ داده باید فردا هم قابل خواندن باشد. localStorage برای تمرین ذخیرهٔ محلی ساده است، اما پایگاه دادهٔ سرور، فضای نامحدود یا محل امن اطلاعات محرمانه نیست. داده به origin وابسته است و در مرورگر کاربر نگه داشته میشود. این درس روی قرارداد فایل، اعتبارسنجی هنگام خواندن و رفتار در شکست تمرکز دارد.
JSON معتبر، الزاماً هزینهٔ معتبر نیست
فایل {"version":1,"items":[]} از نظر JSON درست است؛ ولی اگر amount یک رشته یا تاریخ نامعتبر باشد، برای برنامه قابل قبول نیست. JSON.parse فقط دستور زبان JSON را میخواند. پروژهٔ دفتر هزینه تابع restore را برای بررسی نسخه، تعداد ردیف، اعتبار هر هزینه، شناسهٔ تکراری و مجموع امن دارد. این کنترلها هنگام خواندن از مرورگر و فایل پشتیبان یکسان اجرا میشوند.
مثال اول؛ بازیابی را پیش از تغییر انجام دهید
این کد را در فایل .mjs ریشهٔ پروژهٔ دانلودی با Node اجرا کنید. مدل به DOM وابسته نیست. نمونهٔ خراب باید پیش از جایگزینی دادهٔ فعلی رد شود؛ روی شکست، نباید items را خالی کنیم و به کاربر موفقیت نشان دهیم.
import { restore } from './projects/expenses/model.mjs';
const backup = JSON.stringify({ version: 1, items: [{
id: '11111111-1111-4111-8111-111111111111',
title: 'کتاب', amount: 180000, category: 'آموزش', date: '2026-10-28'
}] });
let current = [];
const candidate = restore(JSON.parse(backup));
current = candidate;
console.log(current.length); // 1
try {
restore({ version: 1, items: [current[0], current[0]] });
} catch (error) {
console.log(error.message); // شناسهٔ تکراری در پشتیبان وجود دارد.
}
console.log(current.length); // هنوز 1متغیر candidate مرز مهمی دارد: همهٔ بررسیها روی دادهٔ پیشنهادی انجام میشود. پس از تأیید و در رابط، پس از اطلاع کاربر از جایگزینی کامل، آن را اعمال میکنیم. ادغام پشتیبان با دادهٔ فعلی قرارداد دیگری است؛ بدون طراحی تشخیص تکرار، آن را «بازیابی» ننامید. پروژهٔ همراه جایگزینی کامل را اجرا میکند و قبل از آن تأیید میگیرد.
مثال دوم؛ ذخیرهسازی را قابل تزریق کنید
در shared/ui.mjs توابع loadJSON و saveJSON وجود دارند. پارامتر اختیاری storage باعث میشود خطای ذخیره را بدون پر کردن حافظهٔ واقعی مرورگر بازتولید کنیم. نمونهٔ زیر هم در Node قابل اجراست؛ چون storage را صریح پاس میدهیم و به window نیاز ندارد.
import { loadJSON, saveJSON } from './projects/shared/ui.mjs';
const memory = new Map();
const storage = {
getItem: key => memory.has(key) ? memory.get(key) : null,
setItem: (key, value) => memory.set(key, value)
};
console.log(saveJSON('demo', { version: 1, items: [] }, storage)); // true
const result = loadJSON('demo', restore, [], storage);
console.log(result.value.length, result.error); // 0 و رشتهٔ خالی
const blocked = {
getItem() { throw Error('read blocked'); },
setItem() { throw Error('quota exceeded'); }
};
console.log(saveJSON('demo', {}, blocked)); // false
console.log(loadJSON('demo', restore, [], blocked).error.length > 0); // trueخطای دسترسی به خود window.localStorage هم ممکن است رخ دهد؛ دسترسی و خواندن باید داخل مسیر try باشند. تابع آماده همین نکته را رعایت میکند. شکست را پنهان نکنید: نمایش فهرست خالی پس از خطای خواندن ممکن است کاربر را به ذخیرهٔ نسخهٔ خالی و از دست دادن شانس بازیابی سوق بدهد. پیام باید فرق «هنوز دادهای نداریم» و «داده خوانده نشد» را مشخص کند.
نسخهبندی و مهاجرت سنجیده
اگر فردا currency اضافه شود، صرف افزودن version:2 مشکل را حل نمیکند. باید مشخص باشد نسخهٔ یک چگونه به دو تبدیل میشود، فیلد تازه چه مقدار پیشفرض معناداری دارد و خروجی مهاجرت دوباره چگونه اعتبارسنجی میشود. پروژهٔ فعلی فقط نسخهٔ یک را پشتیبانی میکند؛ مهاجرت، تمرین توسعهٔ شماست. نسخهٔ ناشناخته را حدس نزنید و رشتهها را بیصدا به عدد تبدیل نکنید.
ذخیرهٔ localStorage همزمان و مبتنی بر رشته است. برای دادهٔ بزرگ، نوشتن مکرر میتواند رابط را متوقف کند و محدودیت فضا دارد. در فایل همراه سقف اندازهٔ فایل ورودی قبل از text بررسی میشود و سپس مدل سقف ردیفها را بررسی میکند. این کار همهٔ خطرهای فایل دلخواه را حل نمیکند، ولی برای یک آزمایشگاه محلی مرزهای روشن میسازد. خروجی JSON رمزگذاری نیست و در پوشهٔ دانلود ممکن است قابل دسترسی دیگران باشد.
تمرین مستقل و پاسخ تشریحی
یک مهاجرت مستقل بسازید که فقط دادهٔ نسخهٔ یک را به نسخهٔ دو با currency:"TOMAN" تبدیل کند. تابع را روی ورودی نسخهٔ ناشناخته، ردیف خراب و ورودی درست امتحان کنید. پاسخ مناسب ابتدا restore نسخهٔ یک را صدا میزند، سپس رکورد تازه میسازد؛ نسخهٔ ناشناخته خطاست. نسخهٔ دو را مستقیماً به restore فعلی ندهید و ادعا نکنید مدل آماده آن را میپذیرد؛ validator نسخهٔ دو را جدا تعریف کنید.
معیار تحویل: فایل خراب دادهٔ فعلی را تغییر ندهد، ذخیرهٔ ناموفق پیام واضح داشته باشد، نسخهٔ ناشناخته رد شود و export سپس import همان تعداد ردیف و مجموع را بازگرداند. دادهٔ آزمایشی بسازید؛ تمرین را روی تنها نسخهٔ اطلاعات شخصی انجام ندهید.
منابع انگلیسی برای مطالعهٔ تکمیلی
https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
تألیف و مثالهای مستقل: تحریریه درسآوند.